studiocms 0.1.0 → 0.2.0
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/CHANGELOG.md +41 -0
- package/dist/cli/add/index.js +2 -2
- package/dist/cli/crypto/genJWT/index.js +4 -3
- package/dist/cli/debug/index.js +2 -3
- package/dist/cli/getTurso/index.js +1 -1
- package/dist/cli/init/index.js +1 -1
- package/dist/cli/init/steps/env.js +3 -2
- package/dist/cli/init/steps/next.js +3 -2
- package/dist/cli/migrator/index.js +5 -6
- package/dist/cli/users/index.js +1 -1
- package/dist/cli/users/shared.js +1 -1
- package/dist/cli/users/steps/createUsers.js +3 -2
- package/dist/cli/users/steps/modifyUsers.js +7 -6
- package/dist/cli/users/steps/next.d.ts +1 -2
- package/dist/cli/users/steps/next.js +3 -3
- package/dist/cli/utils/intro.js +2 -2
- package/dist/consts.js +2 -1
- package/dist/virtuals/auth/core.d.ts +5 -5
- package/dist/virtuals/scripts/StorageFileBrowser.d.ts +7 -0
- package/dist/virtuals/scripts/StorageFileBrowser.js +67 -3
- package/frontend/middleware/_authmap.ts +63 -0
- package/frontend/middleware/index.ts +46 -0
- package/frontend/pages/studiocms_api/dashboard/content/page.ts +4 -1
- package/frontend/pages/studiocms_api/integrations/[type]/_routes/db-studio.ts +16 -17
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/categories.ts +2 -2
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/pages.ts +4 -1
- package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/tags.ts +2 -2
- package/frontend/utils/rest-router.ts +20 -21
- package/package.json +30 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# studiocms
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1197](https://github.com/withstudiocms/studiocms/pull/1197) [`4b542ec`](https://github.com/withstudiocms/studiocms/commit/4b542eca8934996f7ed9eaf1c9f040305ea5e471) Thanks [@Adammatthiesen](https://github.com/Adammatthiesen)! - Tweaks optional dependencies, and chunkSizeWarningLimit for Astro config to prevent warnings from WYSIWYG plugin
|
|
8
|
+
|
|
9
|
+
- [#1219](https://github.com/withstudiocms/studiocms/pull/1219) [`9122ddd`](https://github.com/withstudiocms/studiocms/commit/9122ddd16f9c7ab61c5df227ae7a81edd8620bb0) Thanks [@Adammatthiesen](https://github.com/Adammatthiesen)! - Move to updated and migrated cli-kit package
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#1222](https://github.com/withstudiocms/studiocms/pull/1222) [`0f6b4c7`](https://github.com/withstudiocms/studiocms/commit/0f6b4c74886f09ebf35ee73d5d8579d26f8e534a) Thanks [@Adammatthiesen](https://github.com/Adammatthiesen)! - Tweaks package linking to pnpm catalog
|
|
14
|
+
|
|
15
|
+
- [#1211](https://github.com/withstudiocms/studiocms/pull/1211) [`b269e44`](https://github.com/withstudiocms/studiocms/commit/b269e44d68c8fd0da8eb3147c75b7d1cc899580d) Thanks [@Adammatthiesen](https://github.com/Adammatthiesen)! - Adds regex and proper error handling to prevent illegal characters (non-url-safe) from being used for S3 objects.
|
|
16
|
+
|
|
17
|
+
- [#1220](https://github.com/withstudiocms/studiocms/pull/1220) [`3324f2b`](https://github.com/withstudiocms/studiocms/commit/3324f2be74a6c7d21005d1cc0b4a8695f376c53b) Thanks [@kunjabijukchhe](https://github.com/kunjabijukchhe)! - Fixes an issue where saving a page that does not have `draft` set to true, would previously update the `publishedAt` date value.
|
|
18
|
+
|
|
19
|
+
- [#1214](https://github.com/withstudiocms/studiocms/pull/1214) [`efc10be`](https://github.com/withstudiocms/studiocms/commit/efc10bee20db090fdd75463622c30dda390c50ad) Thanks [@Adammatthiesen](https://github.com/Adammatthiesen)! - Fix: Reworks permission checks for dashboard routes to be at the middleware level to prevent unauthorized access
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [[`0f6b4c7`](https://github.com/withstudiocms/studiocms/commit/0f6b4c74886f09ebf35ee73d5d8579d26f8e534a), [`93e62f6`](https://github.com/withstudiocms/studiocms/commit/93e62f65f779192403361826bc2a7fb997762521), [`2dd709f`](https://github.com/withstudiocms/studiocms/commit/2dd709f7f83efbb64c4ccb83f49db2d589ca9404), [`0f6b4c7`](https://github.com/withstudiocms/studiocms/commit/0f6b4c74886f09ebf35ee73d5d8579d26f8e534a), [`4b542ec`](https://github.com/withstudiocms/studiocms/commit/4b542eca8934996f7ed9eaf1c9f040305ea5e471), [`4b542ec`](https://github.com/withstudiocms/studiocms/commit/4b542eca8934996f7ed9eaf1c9f040305ea5e471), [`e628b43`](https://github.com/withstudiocms/studiocms/commit/e628b431f3128da1ad378138bdda2ca14794e76e), [`8a0ea71`](https://github.com/withstudiocms/studiocms/commit/8a0ea7176350b9526203d5722e1ff45d7fe6dfeb), [`59e5517`](https://github.com/withstudiocms/studiocms/commit/59e5517963cfd5f62fd3631b5ee69ae1e423ef50), [`c68668b`](https://github.com/withstudiocms/studiocms/commit/c68668b0a83341dd6cbdc378e1673017afef1d73)]:
|
|
22
|
+
- @withstudiocms/cli-kit@0.2.0
|
|
23
|
+
- @withstudiocms/effect@0.2.0
|
|
24
|
+
- @withstudiocms/kysely@0.2.0
|
|
25
|
+
- @withstudiocms/sdk@0.2.0
|
|
26
|
+
- @withstudiocms/auth-kit@0.1.2
|
|
27
|
+
- @withstudiocms/component-registry@0.1.2
|
|
28
|
+
|
|
29
|
+
## 0.1.1
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- [#1181](https://github.com/withstudiocms/studiocms/pull/1181) [`a169a89`](https://github.com/withstudiocms/studiocms/commit/a169a893338947b425e87057cc77401f33abcbfd) Thanks [@renovate](https://github.com/apps/renovate)! - fix(deps): update dependency @iconify-json/simple-icons to ^1.2.66
|
|
34
|
+
|
|
35
|
+
- [#1186](https://github.com/withstudiocms/studiocms/pull/1186) [`415a512`](https://github.com/withstudiocms/studiocms/commit/415a51241ffddf5045ad8f8d695a5f40a86b5af7) Thanks [@Adammatthiesen](https://github.com/Adammatthiesen)! - fix workspace package dependency specifiers
|
|
36
|
+
|
|
37
|
+
- [#1187](https://github.com/withstudiocms/studiocms/pull/1187) [`1b2a0c5`](https://github.com/withstudiocms/studiocms/commit/1b2a0c57299544caeba18205ca85a8ca0381d7cb) Thanks [@aliozinan](https://github.com/aliozinan)! - Fix create folder bug in s3-storage PUT request handler
|
|
38
|
+
|
|
39
|
+
- Updated dependencies [[`415a512`](https://github.com/withstudiocms/studiocms/commit/415a51241ffddf5045ad8f8d695a5f40a86b5af7)]:
|
|
40
|
+
- @withstudiocms/component-registry@0.1.1
|
|
41
|
+
- @withstudiocms/auth-kit@0.1.1
|
|
42
|
+
- @withstudiocms/sdk@0.1.1
|
|
43
|
+
|
|
3
44
|
## 0.1.0
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
package/dist/cli/add/index.js
CHANGED
|
@@ -123,7 +123,7 @@ const addPlugin = Cli.Command.make(
|
|
|
123
123
|
{
|
|
124
124
|
plugin
|
|
125
125
|
},
|
|
126
|
-
({ plugin: plugin2 })
|
|
126
|
+
Effect.fn(function* ({ plugin: plugin2 }) {
|
|
127
127
|
const [validator, installer, updater, context] = yield* Effect.all([
|
|
128
128
|
ValidatePlugins,
|
|
129
129
|
TryToInstallPlugins,
|
|
@@ -211,7 +211,7 @@ const addPlugin = Cli.Command.make(
|
|
|
211
211
|
);
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
|
-
}
|
|
214
|
+
}, Effect.provide(addPluginDeps))
|
|
215
215
|
).pipe(Cli.Command.withDescription("Add StudioCMS plugin(s) to your project"));
|
|
216
216
|
export {
|
|
217
217
|
ALIASES,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { styleText } from "node:util";
|
|
2
3
|
import {
|
|
3
4
|
StudioCMSColorway,
|
|
4
5
|
StudioCMSColorwayBg,
|
|
@@ -60,7 +61,7 @@ const genJWT = Cli.Command.make(
|
|
|
60
61
|
const context = yield* genContext;
|
|
61
62
|
const { chalk } = context;
|
|
62
63
|
if (debug2) logger.debug("Init complete, starting...");
|
|
63
|
-
yield* intro(label("StudioCMS Crypto: Generate JWT", StudioCMSColorwayBg,
|
|
64
|
+
yield* intro(label("StudioCMS Crypto: Generate JWT", StudioCMSColorwayBg, "bold"));
|
|
64
65
|
const spin = yield* spinner();
|
|
65
66
|
try {
|
|
66
67
|
yield* spin.start("Getting Key from keyFile");
|
|
@@ -89,14 +90,14 @@ const genJWT = Cli.Command.make(
|
|
|
89
90
|
const base64UrlJwt = yield* convertJwtToBase64Url(jwt);
|
|
90
91
|
yield* spin.stop("Token Generated.");
|
|
91
92
|
yield* log.success(
|
|
92
|
-
boxen(chalk.bold(`${label("Token Generated!", StudioCMSColorwayInfoBg,
|
|
93
|
+
boxen(chalk.bold(`${label("Token Generated!", StudioCMSColorwayInfoBg, "bold")}`), {
|
|
93
94
|
ln1: "Your new Token has been generated successfully:",
|
|
94
95
|
ln3: `Token: ${chalk.magenta(jwt)}`,
|
|
95
96
|
ln5: `Base64Url Token: ${chalk.blue(base64UrlJwt)}`
|
|
96
97
|
})
|
|
97
98
|
);
|
|
98
99
|
yield* outro(
|
|
99
|
-
`${label("You can now use this token where needed.", StudioCMSColorwayBg,
|
|
100
|
+
`${label("You can now use this token where needed.", StudioCMSColorwayBg, "bold")} Stuck? Join us on Discord at ${StudioCMSColorway(styleText(["bold", "underline"], "https://chat.studiocms.dev"))}`
|
|
100
101
|
);
|
|
101
102
|
} catch (err) {
|
|
102
103
|
if (err instanceof Error) {
|
package/dist/cli/debug/index.js
CHANGED
|
@@ -5,7 +5,6 @@ import { loadConfigFile } from "@withstudiocms/config-utils";
|
|
|
5
5
|
import { Cli, Effect } from "@withstudiocms/effect";
|
|
6
6
|
import { intro, log, outro } from "@withstudiocms/effect/clack";
|
|
7
7
|
import { DebugInfoProvider } from "@withstudiocms/internal_helpers/debug-info-provider";
|
|
8
|
-
import chalk from "chalk";
|
|
9
8
|
import { debug } from "../utils/debugOpt.js";
|
|
10
9
|
import { StudioCMSCliError } from "../utils/errors.js";
|
|
11
10
|
const astroConfigPaths = [
|
|
@@ -42,7 +41,7 @@ const loadConfig = (key) => Effect.tryPromise({
|
|
|
42
41
|
const debugCMD = Cli.Command.make(
|
|
43
42
|
"debug",
|
|
44
43
|
{ debug },
|
|
45
|
-
({ debug: _debug })
|
|
44
|
+
Effect.fn(function* ({ debug: _debug }) {
|
|
46
45
|
let debug2;
|
|
47
46
|
if (typeof _debug !== "boolean") {
|
|
48
47
|
debug2 = yield* _debug;
|
|
@@ -88,7 +87,7 @@ const debugCMD = Cli.Command.make(
|
|
|
88
87
|
yield* Effect.log("Debug info gathered successfully.");
|
|
89
88
|
}
|
|
90
89
|
console.log("");
|
|
91
|
-
yield* intro(`${label("StudioCMS Debug Information", StudioCMSColorwayBg,
|
|
90
|
+
yield* intro(`${label("StudioCMS Debug Information", StudioCMSColorwayBg, "black")}`);
|
|
92
91
|
yield* log.info(debugInfo);
|
|
93
92
|
yield* outro();
|
|
94
93
|
if (debug2) {
|
|
@@ -10,7 +10,7 @@ const shell = Cli.Args.text({ name: "shell" }).pipe(
|
|
|
10
10
|
const getTurso = Cli.Command.make(
|
|
11
11
|
"get-turso",
|
|
12
12
|
{ shell },
|
|
13
|
-
({ shell: rawShell })
|
|
13
|
+
Effect.fn(function* ({ shell: rawShell }) {
|
|
14
14
|
let shell2;
|
|
15
15
|
if (typeof rawShell !== "string" && rawShell !== void 0) {
|
|
16
16
|
shell2 = yield* rawShell;
|
package/dist/cli/init/index.js
CHANGED
|
@@ -39,7 +39,7 @@ const initCMD = Cli.Command.make(
|
|
|
39
39
|
debugLogger(`Options: ${JSON.stringify({ debug: debug2, dry }, null, 2)}`),
|
|
40
40
|
debugLogger(`Context: ${JSON.stringify(context, null, 2)}`),
|
|
41
41
|
intro(
|
|
42
|
-
`${label("StudioCMS", StudioCMSColorwayBg,
|
|
42
|
+
`${label("StudioCMS", StudioCMSColorwayBg, "black")} Interactive CLI - initializing...`
|
|
43
43
|
)
|
|
44
44
|
]);
|
|
45
45
|
yield* SCMS_Intro(debug2).pipe(cliContext);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { styleText } from "node:util";
|
|
4
5
|
import {
|
|
5
6
|
StudioCMSColorwayError,
|
|
6
7
|
StudioCMSColorwayInfo,
|
|
@@ -44,7 +45,7 @@ const env = Effect.fn(function* (context, debug, dryRun) {
|
|
|
44
45
|
yield* debugLogger(`Environment file exists: ${envExists}`);
|
|
45
46
|
if (envExists) {
|
|
46
47
|
yield* log.warn(
|
|
47
|
-
`${label("Warning", StudioCMSColorwayWarnBg,
|
|
48
|
+
`${label("Warning", StudioCMSColorwayWarnBg, "black")} An environment file already exists. Would you like to overwrite it?`
|
|
48
49
|
);
|
|
49
50
|
const confirm2 = yield* askToContinue();
|
|
50
51
|
if (!confirm2) {
|
|
@@ -428,7 +429,7 @@ const env = Effect.fn(function* (context, debug, dryRun) {
|
|
|
428
429
|
}
|
|
429
430
|
if (dryRun) {
|
|
430
431
|
context.tasks.push({
|
|
431
|
-
title: `${StudioCMSColorwayInfo
|
|
432
|
+
title: `${StudioCMSColorwayInfo(styleText("bold", "--dry-run"))} ${styleText("dim", "Skipping environment file creation")}`,
|
|
432
433
|
task: async (message) => {
|
|
433
434
|
message("Creating environment file... (skipped)");
|
|
434
435
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { styleText } from "node:util";
|
|
1
2
|
import {
|
|
2
3
|
StudioCMSColorway,
|
|
3
4
|
StudioCMSColorwayBg,
|
|
@@ -33,7 +34,7 @@ const next = (debug) => genLogger("studiocms/cli/init/steps/next")(function* ()
|
|
|
33
34
|
log.success(
|
|
34
35
|
boxen(
|
|
35
36
|
chalk.bold(
|
|
36
|
-
`${label("Init Complete!", StudioCMSColorwayInfoBg,
|
|
37
|
+
`${label("Init Complete!", StudioCMSColorwayInfoBg, "bold")} Get started with StudioCMS:`
|
|
37
38
|
),
|
|
38
39
|
{
|
|
39
40
|
ln1: `Ensure your ${chalk.cyanBright(".env")} file is configured correctly.`,
|
|
@@ -45,7 +46,7 @@ const next = (debug) => genLogger("studiocms/cli/init/steps/next")(function* ()
|
|
|
45
46
|
]);
|
|
46
47
|
yield* Effect.all([
|
|
47
48
|
outro(
|
|
48
|
-
`${label("Enjoy your new CMS!", StudioCMSColorwayBg,
|
|
49
|
+
`${label("Enjoy your new CMS!", StudioCMSColorwayBg, "bold")} Stuck? Join us on Discord at ${StudioCMSColorway(styleText(["bold", "underline"], "https://chat.studiocms.dev"))}`
|
|
49
50
|
),
|
|
50
51
|
debugLogger("Next steps complete")
|
|
51
52
|
]);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { styleText } from "node:util";
|
|
2
3
|
import { StudioCMSColorwayBg, StudioCMSColorwayInfoBg } from "@withstudiocms/cli-kit/colors";
|
|
3
4
|
import { label } from "@withstudiocms/cli-kit/messages";
|
|
4
5
|
import { Cli, Effect, runEffect } from "@withstudiocms/effect";
|
|
@@ -102,9 +103,7 @@ const migratorCMD = Cli.Command.make(
|
|
|
102
103
|
debugLogger("Starting interactive CLI..."),
|
|
103
104
|
debugLogger(`Options: ${JSON.stringify({ debug: debug2, rollback: rollback2 }, null, 2)}`),
|
|
104
105
|
debugLogger(`Context: ${JSON.stringify(context, null, 2)}`),
|
|
105
|
-
intro(
|
|
106
|
-
`${label("StudioCMS Migrator", StudioCMSColorwayBg, context.chalk.black)} - initializing...`
|
|
107
|
-
)
|
|
106
|
+
intro(`${label("StudioCMS Migrator", StudioCMSColorwayBg, "black")} - initializing...`)
|
|
108
107
|
]);
|
|
109
108
|
const isRollback = rollback2 && !latest2 && !status2;
|
|
110
109
|
const isLatest = !rollback2 && latest2 && !status2;
|
|
@@ -146,11 +145,11 @@ const migratorCMD = Cli.Command.make(
|
|
|
146
145
|
const migrationPercent = appliedMigrations / migrationTotal * 100;
|
|
147
146
|
const migrationTotalColor = migrationPercent === 100 ? context.chalk.green : migrationPercent > 50 ? context.chalk.yellow : context.chalk.red;
|
|
148
147
|
const labelParts = [
|
|
149
|
-
label("Migration Status", StudioCMSColorwayInfoBg,
|
|
148
|
+
label("Migration Status", StudioCMSColorwayInfoBg, "black"),
|
|
150
149
|
label(
|
|
151
150
|
`(${context.chalk.green(appliedMigrations.toString())}/${migrationTotalColor(migrationTotal.toString())}) Applied`,
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
(text) => styleText("bold", text),
|
|
152
|
+
"black"
|
|
154
153
|
)
|
|
155
154
|
];
|
|
156
155
|
const responseMessage = `${labelParts.join(" ")}
|
package/dist/cli/users/index.js
CHANGED
|
@@ -51,7 +51,7 @@ const usersCMD = Cli.Command.make(
|
|
|
51
51
|
yield* Effect.all([
|
|
52
52
|
debugLogger(`Context: ${JSON.stringify(context, null, 2)}`),
|
|
53
53
|
intro(
|
|
54
|
-
`${label("StudioCMS", StudioCMSColorwayBg,
|
|
54
|
+
`${label("StudioCMS", StudioCMSColorwayBg, "black")} Interactive CLI - initializing...`
|
|
55
55
|
),
|
|
56
56
|
checkRequiredEnvVarsEffect(["CMS_ENCRYPTION_KEY"])
|
|
57
57
|
]);
|
package/dist/cli/users/shared.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "@withstudiocms/effect";
|
|
2
2
|
import { log } from "@withstudiocms/effect/clack";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
const validateInputOrRePrompt =
|
|
4
|
+
const validateInputOrRePrompt = Effect.fn("validateInputOrRePrompt")(function* (opts) {
|
|
5
5
|
const { prompt, checkEffect } = opts;
|
|
6
6
|
while (true) {
|
|
7
7
|
const input = yield* prompt;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { styleText } from "node:util";
|
|
1
2
|
import { getAvatarUrl } from "@withstudiocms/auth-kit/utils/libravatar";
|
|
2
3
|
import { StudioCMSColorwayInfo } from "@withstudiocms/cli-kit/colors";
|
|
3
4
|
import { group, log, password, select, text } from "@withstudiocms/effect/clack";
|
|
@@ -150,14 +151,14 @@ const createUsers = Effect.fn(function* (context, _debug, dryRun) {
|
|
|
150
151
|
const todo = Effect.all([_createUser(newUser), _createRank(newRank)]);
|
|
151
152
|
if (dryRun) {
|
|
152
153
|
context.tasks.push({
|
|
153
|
-
title: `${StudioCMSColorwayInfo
|
|
154
|
+
title: `${StudioCMSColorwayInfo(styleText("bold", "--dry-run"))} ${styleText("dim", "Skipping user creation")}`,
|
|
154
155
|
task: async (message) => {
|
|
155
156
|
message("Creating user... (skipped)");
|
|
156
157
|
}
|
|
157
158
|
});
|
|
158
159
|
} else {
|
|
159
160
|
context.tasks.push({
|
|
160
|
-
title:
|
|
161
|
+
title: styleText("dim", "Creating user..."),
|
|
161
162
|
task: async (message) => {
|
|
162
163
|
try {
|
|
163
164
|
const [insertedUser, insertedRank] = await runEffect(todo);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { styleText } from "node:util";
|
|
1
2
|
import { StudioCMSColorwayError, StudioCMSColorwayInfo } from "@withstudiocms/cli-kit/colors";
|
|
2
3
|
import { runEffect } from "@withstudiocms/effect";
|
|
3
4
|
import { log, note, password, select, text } from "@withstudiocms/effect/clack";
|
|
@@ -125,14 +126,14 @@ const modifyUsers = Effect.fn(function* (context, _debug, dryRun) {
|
|
|
125
126
|
}
|
|
126
127
|
if (dryRun) {
|
|
127
128
|
context.tasks.push({
|
|
128
|
-
title: `${StudioCMSColorwayInfo
|
|
129
|
+
title: `${StudioCMSColorwayInfo(styleText("bold", "--dry-run"))} ${styleText("dim", "Skipping user modification")}`,
|
|
129
130
|
task: async (message) => {
|
|
130
131
|
message("Modifying user... (skipped)");
|
|
131
132
|
}
|
|
132
133
|
});
|
|
133
134
|
} else {
|
|
134
135
|
context.tasks.push({
|
|
135
|
-
title:
|
|
136
|
+
title: styleText("dim", "Modifying user..."),
|
|
136
137
|
task: async (message) => {
|
|
137
138
|
try {
|
|
138
139
|
await runEffect(
|
|
@@ -166,14 +167,14 @@ const modifyUsers = Effect.fn(function* (context, _debug, dryRun) {
|
|
|
166
167
|
}
|
|
167
168
|
if (dryRun) {
|
|
168
169
|
context.tasks.push({
|
|
169
|
-
title: `${StudioCMSColorwayInfo
|
|
170
|
+
title: `${StudioCMSColorwayInfo(styleText("bold", "--dry-run"))} ${styleText("dim", "Skipping user modification")}`,
|
|
170
171
|
task: async (message) => {
|
|
171
172
|
message("Modifying user... (skipped)");
|
|
172
173
|
}
|
|
173
174
|
});
|
|
174
175
|
} else {
|
|
175
176
|
context.tasks.push({
|
|
176
|
-
title:
|
|
177
|
+
title: styleText("dim", "Modifying user..."),
|
|
177
178
|
task: async (message) => {
|
|
178
179
|
try {
|
|
179
180
|
await runEffect(
|
|
@@ -207,14 +208,14 @@ const modifyUsers = Effect.fn(function* (context, _debug, dryRun) {
|
|
|
207
208
|
}
|
|
208
209
|
if (dryRun) {
|
|
209
210
|
context.tasks.push({
|
|
210
|
-
title: `${StudioCMSColorwayInfo
|
|
211
|
+
title: `${StudioCMSColorwayInfo(styleText("bold", "--dry-run"))} ${styleText("dim", "Skipping user modification")}`,
|
|
211
212
|
task: async (message) => {
|
|
212
213
|
message("Modifying user... (skipped)");
|
|
213
214
|
}
|
|
214
215
|
});
|
|
215
216
|
} else {
|
|
216
217
|
context.tasks.push({
|
|
217
|
-
title:
|
|
218
|
+
title: styleText("dim", "Modifying user..."),
|
|
218
219
|
task: async (message) => {
|
|
219
220
|
try {
|
|
220
221
|
const hashedPassword = await runEffect(hashPassword(newPassword));
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { Effect } from '../../../effect.js';
|
|
2
|
-
|
|
3
|
-
export declare const next: (debug: boolean) => Effect.Effect<void, import("effect/Cause").UnknownException | import("effect/ConfigError").ConfigError | import("@withstudiocms/effect/clack").ClackError, CliContext>;
|
|
2
|
+
export declare const next: (debug: boolean) => Effect.Effect<void, import("effect/Cause").UnknownException | import("effect/ConfigError").ConfigError | import("@withstudiocms/effect/clack").ClackError, never>;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { styleText } from "node:util";
|
|
1
2
|
import { StudioCMSColorway, StudioCMSColorwayBg } from "@withstudiocms/cli-kit/colors";
|
|
2
3
|
import { label } from "@withstudiocms/cli-kit/messages";
|
|
3
4
|
import { outro } from "@withstudiocms/effect/clack";
|
|
4
5
|
import { Effect, genLogger } from "../../../effect.js";
|
|
5
|
-
import { CliContext } from "../../utils/context.js";
|
|
6
6
|
import { buildDebugLogger } from "../../utils/logger.js";
|
|
7
7
|
const next = (debug) => genLogger("studiocms/cli/users/steps/next")(function* () {
|
|
8
|
-
const
|
|
8
|
+
const debugLogger = yield* buildDebugLogger(debug);
|
|
9
9
|
yield* Effect.all([
|
|
10
10
|
outro(
|
|
11
|
-
`${label("Action Complete!", StudioCMSColorwayBg,
|
|
11
|
+
`${label("Action Complete!", StudioCMSColorwayBg, "bold")} Stuck? Join us on Discord at ${StudioCMSColorway(styleText(["bold", "underline"], "https://chat.studiocms.dev"))}`
|
|
12
12
|
),
|
|
13
13
|
debugLogger("Next steps complete")
|
|
14
14
|
]);
|
package/dist/cli/utils/intro.js
CHANGED
|
@@ -11,7 +11,7 @@ const random = (...arr) => {
|
|
|
11
11
|
return flattenedArray[Math.floor(flattenedArray.length * Math.random())];
|
|
12
12
|
};
|
|
13
13
|
const intro = (debug) => genLogger("studiocms/cli/utils/intro")(function* () {
|
|
14
|
-
const {
|
|
14
|
+
const { username } = yield* CliContext;
|
|
15
15
|
const { messages } = getSeasonalMessages();
|
|
16
16
|
const welcome = random(messages);
|
|
17
17
|
debug && logger.debug("Printing welcome message...");
|
|
@@ -21,7 +21,7 @@ const intro = (debug) => genLogger("studiocms/cli/utils/intro")(function* () {
|
|
|
21
21
|
[
|
|
22
22
|
"Welcome",
|
|
23
23
|
"to",
|
|
24
|
-
label("StudioCMS", StudioCMSColorwayBg,
|
|
24
|
+
label("StudioCMS", StudioCMSColorwayBg, "black"),
|
|
25
25
|
StudioCMSColorway(`v${pkgJson.version}`),
|
|
26
26
|
username
|
|
27
27
|
],
|
package/dist/consts.js
CHANGED
|
@@ -93,7 +93,8 @@ const AstroConfigImageSettings = {
|
|
|
93
93
|
};
|
|
94
94
|
const AstroConfigViteSettings = {
|
|
95
95
|
build: {
|
|
96
|
-
chunkSizeWarningLimit:
|
|
96
|
+
chunkSizeWarningLimit: 1200,
|
|
97
|
+
// Allow's increase to 1.2 kB for Plugins such as our WYSIWYG editor to not trigger warnings
|
|
97
98
|
rollupOptions: {
|
|
98
99
|
// Users will need to install these peer dependencies themselves
|
|
99
100
|
external: ["@libsql/client", "kysely-turso", "pg", "mysql2"]
|
|
@@ -34,10 +34,10 @@ export declare const Encryption: Effect.Effect<{
|
|
|
34
34
|
readonly createSession: (token: string, userId: string) => Effect.Effect<import("@withstudiocms/auth-kit/types").UserSession, import("@withstudiocms/auth-kit/errors").SessionError, never>;
|
|
35
35
|
readonly validateSessionToken: (token: string) => Effect.Effect<import("@withstudiocms/auth-kit/types").SessionValidationResult, import("@withstudiocms/auth-kit/errors").SessionError, never>;
|
|
36
36
|
readonly invalidateSession: (sessionId: string) => Effect.Effect<void, import("@withstudiocms/auth-kit/errors").SessionError, never>;
|
|
37
|
-
readonly setSessionTokenCookie: (context: import("astro").APIContext
|
|
38
|
-
readonly deleteSessionTokenCookie: (context: import("astro").APIContext
|
|
39
|
-
readonly setOAuthSessionTokenCookie: (context: import("astro").APIContext
|
|
40
|
-
readonly createUserSession: (userId: string, context: import("astro").APIContext
|
|
37
|
+
readonly setSessionTokenCookie: (context: import("astro").APIContext | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>, token: string, expiresAt: Date, secure?: boolean | undefined) => Effect.Effect<void, import("@withstudiocms/auth-kit/errors").SessionError, never>;
|
|
38
|
+
readonly deleteSessionTokenCookie: (context: import("astro").APIContext | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>, secure?: boolean | undefined) => Effect.Effect<void, import("@withstudiocms/auth-kit/errors").SessionError, never>;
|
|
39
|
+
readonly setOAuthSessionTokenCookie: (context: import("astro").APIContext | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>, key: string, value: string, secure?: boolean | undefined) => Effect.Effect<void, import("@withstudiocms/auth-kit/errors").SessionError, never>;
|
|
40
|
+
readonly createUserSession: (userId: string, context: import("astro").APIContext | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>, secure?: boolean | undefined) => Effect.Effect<void, import("@withstudiocms/auth-kit/errors").SessionError, never>;
|
|
41
41
|
}, import("@withstudiocms/auth-kit/errors").SessionError, never>, User: Effect.Effect<{
|
|
42
42
|
readonly verifyUsernameInput: (username: string) => Effect.Effect<string | true, import("@withstudiocms/auth-kit/errors").CheckIfUnsafeError | import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
43
43
|
readonly createUserAvatar: (email: string) => Effect.Effect<string, import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
@@ -49,7 +49,7 @@ export declare const Encryption: Effect.Effect<{
|
|
|
49
49
|
readonly updateUserPassword: (userId: string, password: string) => Effect.Effect<import("@withstudiocms/auth-kit/types").UserData, import("@withstudiocms/effect/scrypt").ScryptError | import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
50
50
|
readonly getUserPasswordHash: (userId: string) => Effect.Effect<string, import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
51
51
|
readonly getUserFromEmail: (email: string) => Effect.Effect<import("@withstudiocms/auth-kit/types").CombinedUserData | null | undefined, import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
52
|
-
readonly getUserData: (context: import("astro").APIContext
|
|
52
|
+
readonly getUserData: (context: import("astro").APIContext | import("astro").AstroGlobal<Record<string, any>, import("astro/runtime/server/index.js").AstroComponentFactory, Record<string, string | undefined>>) => Effect.Effect<import("@withstudiocms/auth-kit/types").UserSessionData, import("@withstudiocms/auth-kit/errors").SessionError | import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
53
53
|
readonly getUserPermissionLevel: (userData: import("@withstudiocms/auth-kit/types").UserSessionData | import("@withstudiocms/auth-kit/types").CombinedUserData | null) => Effect.Effect<import("@withstudiocms/auth-kit/types").UserPermissionLevel, import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
54
54
|
readonly isUserAllowed: (userData: import("@withstudiocms/auth-kit/types").UserSessionData | import("@withstudiocms/auth-kit/types").CombinedUserData | null, requiredPerms: "owner" | "admin" | "editor" | "visitor" | "unknown") => Effect.Effect<boolean, import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
55
55
|
}, import("@withstudiocms/auth-kit/errors").SessionError | import("@withstudiocms/auth-kit/errors").UserError, never>;
|
|
@@ -7,6 +7,7 @@ interface MimeTypeMap {
|
|
|
7
7
|
[key: string]: string;
|
|
8
8
|
}
|
|
9
9
|
type StorageReturnType = 'url' | 'identifier' | 'key';
|
|
10
|
+
declare const s3SafeNameRegex: RegExp;
|
|
10
11
|
/**
|
|
11
12
|
* Translation strings for the StorageFileBrowser
|
|
12
13
|
*/
|
|
@@ -77,6 +78,11 @@ interface TranslationStrings {
|
|
|
77
78
|
andAllItsContents: string;
|
|
78
79
|
filePreview: string;
|
|
79
80
|
}
|
|
81
|
+
declare class InvalidFileNameError extends Error {
|
|
82
|
+
#private;
|
|
83
|
+
constructor(files: string[]);
|
|
84
|
+
get files(): string[];
|
|
85
|
+
}
|
|
80
86
|
/**
|
|
81
87
|
* StorageFileBrowser Custom Element
|
|
82
88
|
* A web component for browsing and selecting files from cloud storage
|
|
@@ -155,6 +161,7 @@ declare class StorageFileBrowser extends HTMLElement {
|
|
|
155
161
|
private deleteFolder;
|
|
156
162
|
private showCreateFolderDialog;
|
|
157
163
|
private createFolder;
|
|
164
|
+
private escapeHtml;
|
|
158
165
|
private uploadFilesWithCustomNames;
|
|
159
166
|
private uploadSingleFile;
|
|
160
167
|
private showRenameFolderDialog;
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
const s3SafeNameRegex = /^[a-zA-Z0-9._-]+(?:\/[a-zA-Z0-9._-]+)*$/;
|
|
2
|
+
class InvalidFileNameError extends Error {
|
|
3
|
+
#files;
|
|
4
|
+
constructor(files) {
|
|
5
|
+
super(
|
|
6
|
+
"The following filenames are invalid: (Only alphanumeric characters and . _ - / are allowed.)"
|
|
7
|
+
);
|
|
8
|
+
this.#files = files;
|
|
9
|
+
this.name = "InvalidFileNameError";
|
|
10
|
+
}
|
|
11
|
+
get files() {
|
|
12
|
+
return this.#files;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
1
15
|
class StorageFileBrowser extends HTMLElement {
|
|
2
16
|
currentPath = "";
|
|
3
17
|
selectedFile = null;
|
|
@@ -1208,7 +1222,7 @@ class StorageFileBrowser extends HTMLElement {
|
|
|
1208
1222
|
method: "PUT",
|
|
1209
1223
|
headers: {
|
|
1210
1224
|
"Content-Type": "text/plain",
|
|
1211
|
-
"x-
|
|
1225
|
+
"x-storage-key": folderKey
|
|
1212
1226
|
},
|
|
1213
1227
|
body: ""
|
|
1214
1228
|
});
|
|
@@ -1230,8 +1244,27 @@ class StorageFileBrowser extends HTMLElement {
|
|
|
1230
1244
|
setTimeout(() => this.loadFiles(), 2e3);
|
|
1231
1245
|
}
|
|
1232
1246
|
}
|
|
1247
|
+
escapeHtml(value) {
|
|
1248
|
+
return value.replace(/[&<>"']/g, (char) => {
|
|
1249
|
+
switch (char) {
|
|
1250
|
+
case "&":
|
|
1251
|
+
return "&";
|
|
1252
|
+
case "<":
|
|
1253
|
+
return "<";
|
|
1254
|
+
case ">":
|
|
1255
|
+
return ">";
|
|
1256
|
+
case '"':
|
|
1257
|
+
return """;
|
|
1258
|
+
case "'":
|
|
1259
|
+
return "'";
|
|
1260
|
+
default:
|
|
1261
|
+
return char;
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1233
1265
|
async uploadFilesWithCustomNames(customNames) {
|
|
1234
1266
|
if (this.isUploading) return;
|
|
1267
|
+
const status = { valid: true, invalidFiles: [] };
|
|
1235
1268
|
this.isUploading = true;
|
|
1236
1269
|
const content = this.$(`#${this.contentId}`);
|
|
1237
1270
|
if (!content) return;
|
|
@@ -1254,6 +1287,16 @@ class StorageFileBrowser extends HTMLElement {
|
|
|
1254
1287
|
".storage-browser-progress-text"
|
|
1255
1288
|
);
|
|
1256
1289
|
try {
|
|
1290
|
+
this.pendingFiles.forEach((file, index) => {
|
|
1291
|
+
const customName = customNames[index];
|
|
1292
|
+
if (customName && !s3SafeNameRegex.test(customName)) {
|
|
1293
|
+
status.valid = false;
|
|
1294
|
+
status.invalidFiles.push(file.name);
|
|
1295
|
+
}
|
|
1296
|
+
});
|
|
1297
|
+
if (!status.valid) {
|
|
1298
|
+
throw new InvalidFileNameError(status.invalidFiles);
|
|
1299
|
+
}
|
|
1257
1300
|
for (let i = 0; i < this.pendingFiles.length; i++) {
|
|
1258
1301
|
const file = this.pendingFiles[i];
|
|
1259
1302
|
const customName = customNames[i] || `${Date.now()}-${file.name}`;
|
|
@@ -1271,13 +1314,22 @@ class StorageFileBrowser extends HTMLElement {
|
|
|
1271
1314
|
await this.loadFiles();
|
|
1272
1315
|
} catch (error) {
|
|
1273
1316
|
console.error("Upload error:", error);
|
|
1317
|
+
let errorMessage = this.t("failedToUploadFiles");
|
|
1318
|
+
let extraInfo;
|
|
1319
|
+
if (error instanceof Error) {
|
|
1320
|
+
errorMessage = error.message;
|
|
1321
|
+
if (error instanceof InvalidFileNameError) {
|
|
1322
|
+
extraInfo = this.escapeHtml(error.files.join(", "));
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1274
1325
|
content.innerHTML = `
|
|
1275
1326
|
<div class="storage-browser-error" role="alert" aria-live="assertive">
|
|
1276
1327
|
<svg width="48" height="48" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
1277
1328
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
1278
1329
|
</svg>
|
|
1279
1330
|
<p>${this.t("failedToUploadFiles")}</p>
|
|
1280
|
-
<p style="font-size: 0.875rem; opacity: 0.7;">${
|
|
1331
|
+
<p style="font-size: 0.875rem; opacity: 0.7;">${errorMessage}</p>
|
|
1332
|
+
${extraInfo ? `<p style="font-size: 0.75rem; opacity: 0.5;">${extraInfo}</p>` : ""}
|
|
1281
1333
|
</div>
|
|
1282
1334
|
`;
|
|
1283
1335
|
} finally {
|
|
@@ -1410,6 +1462,9 @@ class StorageFileBrowser extends HTMLElement {
|
|
|
1410
1462
|
</div>
|
|
1411
1463
|
`;
|
|
1412
1464
|
try {
|
|
1465
|
+
if (!s3SafeNameRegex.test(newFileName)) {
|
|
1466
|
+
throw new InvalidFileNameError([newFileName]);
|
|
1467
|
+
}
|
|
1413
1468
|
const pathParts = this.fileToRename.key.split("/");
|
|
1414
1469
|
const _oldFileName = pathParts.pop() || "";
|
|
1415
1470
|
const path = pathParts.length > 0 ? `${pathParts.join("/")}/` : "";
|
|
@@ -1437,13 +1492,22 @@ class StorageFileBrowser extends HTMLElement {
|
|
|
1437
1492
|
await this.loadFiles();
|
|
1438
1493
|
} catch (error) {
|
|
1439
1494
|
console.error("Rename error:", error);
|
|
1495
|
+
let errorMessage = this.t("unknownError");
|
|
1496
|
+
let extraInfo;
|
|
1497
|
+
if (error instanceof Error) {
|
|
1498
|
+
errorMessage = error.message;
|
|
1499
|
+
if (error instanceof InvalidFileNameError) {
|
|
1500
|
+
extraInfo = this.escapeHtml(error.files.join(", "));
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1440
1503
|
content.innerHTML = `
|
|
1441
1504
|
<div class="storage-browser-error" role="alert">
|
|
1442
1505
|
<svg width="48" height="48" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1443
1506
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
1444
1507
|
</svg>
|
|
1445
1508
|
<p>${this.t("failedToRenameFile")}</p>
|
|
1446
|
-
<p style="font-size: 0.875rem; opacity: 0.7;">${
|
|
1509
|
+
<p style="font-size: 0.875rem; opacity: 0.7;">${errorMessage}</p>
|
|
1510
|
+
${extraInfo ? `<p style="font-size: 0.75rem; opacity: 0.5;">${extraInfo}</p>` : ""}
|
|
1447
1511
|
</div>
|
|
1448
1512
|
`;
|
|
1449
1513
|
setTimeout(() => this.loadFiles(), 2e3);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { dashboardConfig } from 'studiocms:config';
|
|
2
|
+
|
|
3
|
+
type AuthenticatedRoute = {
|
|
4
|
+
pathname: string;
|
|
5
|
+
requiredPermissionLevel: 'owner' | 'admin' | 'editor' | 'visitor';
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Import the dashboard route override from the configuration
|
|
9
|
+
// If no override is set, it defaults to 'dashboard'
|
|
10
|
+
// This allows for flexibility in the dashboard route without hardcoding it
|
|
11
|
+
const dashboardRoute = dashboardConfig.dashboardRouteOverride || 'dashboard';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* List of authenticated routes with their required permission levels.
|
|
15
|
+
* This list is used to determine if a user has the necessary permissions
|
|
16
|
+
* to access specific dashboard routes.
|
|
17
|
+
*/
|
|
18
|
+
export const authenticatedRoutes: AuthenticatedRoute[] = [
|
|
19
|
+
{
|
|
20
|
+
pathname: `/${dashboardRoute}/system-management`,
|
|
21
|
+
requiredPermissionLevel: 'owner',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
pathname: `/${dashboardRoute}/smtp-configuration`,
|
|
25
|
+
requiredPermissionLevel: 'owner',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pathname: `/${dashboardRoute}`,
|
|
29
|
+
requiredPermissionLevel: 'editor',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
pathname: `/${dashboardRoute}/user-management`,
|
|
33
|
+
requiredPermissionLevel: 'admin',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pathname: `/${dashboardRoute}/user-management/**`,
|
|
37
|
+
requiredPermissionLevel: 'admin',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
pathname: `/${dashboardRoute}/taxonomy`,
|
|
41
|
+
requiredPermissionLevel: 'editor',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
pathname: `/${dashboardRoute}/taxonomy/categories`,
|
|
45
|
+
requiredPermissionLevel: 'editor',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
pathname: `/${dashboardRoute}/taxonomy/tags`,
|
|
49
|
+
requiredPermissionLevel: 'editor',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
pathname: `/${dashboardRoute}/plugins/**`,
|
|
53
|
+
requiredPermissionLevel: 'editor',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pathname: `/${dashboardRoute}/content-management`,
|
|
57
|
+
requiredPermissionLevel: 'editor',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
pathname: `/${dashboardRoute}/content-management/**`,
|
|
61
|
+
requiredPermissionLevel: 'editor',
|
|
62
|
+
},
|
|
63
|
+
];
|
|
@@ -19,7 +19,9 @@ import { SDKCore } from 'studiocms:sdk';
|
|
|
19
19
|
import SCMSUiVersion from 'studiocms:ui/version';
|
|
20
20
|
import SCMSVersion from 'studiocms:version';
|
|
21
21
|
import { defineMiddlewareRouter, Effect } from '@withstudiocms/effect';
|
|
22
|
+
import micromatch from 'micromatch';
|
|
22
23
|
import { STUDIOCMS_EDITOR_CSRF_COOKIE_NAME } from '#consts';
|
|
24
|
+
import { authenticatedRoutes } from './_authmap.js';
|
|
23
25
|
import { getUserPermissions, makeFallbackSiteConfig, SetLocal, setLocals } from './utils.js';
|
|
24
26
|
|
|
25
27
|
// Import the dashboard route override from the configuration
|
|
@@ -145,6 +147,50 @@ export const onRequest = defineMiddlewareRouter([
|
|
|
145
147
|
// Check if the user is logged in and redirect to the login page if not
|
|
146
148
|
if (!userSessionData.isLoggedIn) return context.redirect(StudioCMSRoutes.authLinks.loginURL);
|
|
147
149
|
|
|
150
|
+
// Get the current path
|
|
151
|
+
const currentPath = context.url.pathname;
|
|
152
|
+
|
|
153
|
+
// Check if the user has permission to access the current route
|
|
154
|
+
// If not, redirect to the dashboard home page
|
|
155
|
+
const userPermissionLevel =
|
|
156
|
+
context.locals.StudioCMS.security?.userSessionData.permissionLevel;
|
|
157
|
+
|
|
158
|
+
if (!userPermissionLevel) {
|
|
159
|
+
// How did the user get here? Log them out to reset session
|
|
160
|
+
return context.redirect(`/${dashboardRoute}/logout`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Using micromatch to handle wildcard route matching
|
|
164
|
+
const matchChance1 = authenticatedRoutes.find((route) =>
|
|
165
|
+
micromatch.isMatch(currentPath, route.pathname)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// if trailing `/` exists, try matching without it
|
|
169
|
+
const matchChance2 =
|
|
170
|
+
matchChance1 ||
|
|
171
|
+
(() => {
|
|
172
|
+
if (currentPath.endsWith('/')) {
|
|
173
|
+
const trimmedPath = currentPath.slice(0, -1);
|
|
174
|
+
return authenticatedRoutes.find((route) =>
|
|
175
|
+
micromatch.isMatch(trimmedPath, route.pathname)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
})();
|
|
180
|
+
|
|
181
|
+
if (matchChance1 || matchChance2) {
|
|
182
|
+
// biome-ignore lint/style/noNonNullAssertion: only used after checking for existence
|
|
183
|
+
const matchingRoute = matchChance1 || matchChance2!;
|
|
184
|
+
const requiredLevel = matchingRoute.requiredPermissionLevel;
|
|
185
|
+
const levels = ['visitor', 'editor', 'admin', 'owner'];
|
|
186
|
+
const userLevelIndex = levels.indexOf(userPermissionLevel);
|
|
187
|
+
const requiredLevelIndex = levels.indexOf(requiredLevel);
|
|
188
|
+
|
|
189
|
+
if (userLevelIndex < requiredLevelIndex) {
|
|
190
|
+
return context.redirect(`/${dashboardRoute}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
148
194
|
// Else, Continue to the next middleware
|
|
149
195
|
return next();
|
|
150
196
|
}),
|
|
@@ -225,7 +225,10 @@ export const { POST, PATCH, DELETE, OPTIONS, ALL } = createEffectAPIRoutes(
|
|
|
225
225
|
authorId: AuthorId,
|
|
226
226
|
contributorIds: JSON.stringify(ContributorIds),
|
|
227
227
|
updatedAt: new Date().toISOString(),
|
|
228
|
-
publishedAt:
|
|
228
|
+
publishedAt:
|
|
229
|
+
currentPageData.draft && data.draft === false
|
|
230
|
+
? new Date().toISOString()
|
|
231
|
+
: currentPageData.publishedAt?.toISOString() || new Date().toISOString(),
|
|
229
232
|
categories: JSON.stringify(data.categories || []),
|
|
230
233
|
tags: JSON.stringify(data.tags || []),
|
|
231
234
|
augments: JSON.stringify(data.augments || []),
|
|
@@ -23,24 +23,23 @@ const useDriverErrorPromise = <T>(_try: () => Promise<T>) =>
|
|
|
23
23
|
new DriverError({ message: error instanceof Error ? error.message : String(error) }),
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
const getDriverInstance = ()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
26
|
+
const getDriverInstance = Effect.fn('getDriverInstance')(function* () {
|
|
27
|
+
// Return existing driver if already initialized
|
|
28
|
+
if (driver) return driver;
|
|
29
|
+
|
|
30
|
+
// Attempt to get and initialize the driver
|
|
31
|
+
driver = yield* useDriverErrorPromise(() => getDriver()).pipe(
|
|
32
|
+
Effect.tap((drv) => useDriverErrorPromise(() => drv.init()))
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// If driver is still undefined, return an error
|
|
36
|
+
if (!driver) {
|
|
37
|
+
return yield* new DriverError({ message: 'Failed to get database driver' });
|
|
38
|
+
}
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
// Return the initialized driver
|
|
41
|
+
return driver;
|
|
42
|
+
});
|
|
44
43
|
|
|
45
44
|
const parseLogLevel = (
|
|
46
45
|
level: 'All' | 'Fatal' | 'Error' | 'Warning' | 'Info' | 'Debug' | 'Trace' | 'None'
|
|
@@ -76,8 +76,8 @@ export const categoriesRouter: EndpointRoute = {
|
|
|
76
76
|
ctx,
|
|
77
77
|
StudioCMSPageDataCategories.Insert.omit('id')
|
|
78
78
|
).pipe(
|
|
79
|
-
Effect.flatMap(
|
|
80
|
-
Effect.
|
|
79
|
+
Effect.flatMap(
|
|
80
|
+
Effect.fn(function* (data) {
|
|
81
81
|
const id = yield* sdk.UTIL.Generators.generateRandomIDNumber(9);
|
|
82
82
|
return { id, ...data };
|
|
83
83
|
})
|
|
@@ -230,7 +230,10 @@ const pageIdRouter = (id: string) =>
|
|
|
230
230
|
authorId: AuthorId,
|
|
231
231
|
contributorIds: JSON.stringify(ContributorIds),
|
|
232
232
|
updatedAt: new Date().toISOString(),
|
|
233
|
-
publishedAt:
|
|
233
|
+
publishedAt:
|
|
234
|
+
currentPageData.draft && data.draft === false
|
|
235
|
+
? new Date().toISOString()
|
|
236
|
+
: currentPageData.publishedAt?.toISOString() || new Date().toISOString(),
|
|
234
237
|
categories: JSON.stringify(data.categories || []),
|
|
235
238
|
tags: JSON.stringify(data.tags || []),
|
|
236
239
|
augments: JSON.stringify(data.augments || []),
|
|
@@ -65,8 +65,8 @@ export const tagsRouter: EndpointRoute = {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
return yield* parseAPIContextJson(ctx, StudioCMSPageDataTags.Insert.omit('id')).pipe(
|
|
68
|
-
Effect.flatMap(
|
|
69
|
-
Effect.
|
|
68
|
+
Effect.flatMap(
|
|
69
|
+
Effect.fn(function* (data) {
|
|
70
70
|
const id = yield* sdk.UTIL.Generators.generateRandomIDNumber(9);
|
|
71
71
|
return { id, ...data };
|
|
72
72
|
})
|
|
@@ -218,34 +218,33 @@ export function idOrPathRouter(
|
|
|
218
218
|
* const responseEffect = processHandler(myHandler, apiContext, '/my-path', 'GET');
|
|
219
219
|
* ```
|
|
220
220
|
*/
|
|
221
|
-
const processHandler = (
|
|
221
|
+
const processHandler = Effect.fn('processHandler')(function* (
|
|
222
222
|
handler: APIRoute | undefined,
|
|
223
223
|
ctx: APIContext,
|
|
224
224
|
path: string,
|
|
225
225
|
method: string
|
|
226
|
-
): Effect.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
226
|
+
): Effect.fn.Return<Response, never, never> {
|
|
227
|
+
if (!handler) {
|
|
228
|
+
return AllResponse();
|
|
229
|
+
}
|
|
231
230
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
)
|
|
231
|
+
const response = yield* Effect.tryPromise({
|
|
232
|
+
try: async () => await handler(ctx),
|
|
233
|
+
catch: (error) =>
|
|
234
|
+
new StudioCMSAPIError({
|
|
235
|
+
message: `Error in handler for path ${path} [${method}]: ${String(error)}`,
|
|
236
|
+
cause: error,
|
|
237
|
+
}),
|
|
238
|
+
}).pipe(
|
|
239
|
+
Effect.catchAll((error) =>
|
|
240
|
+
Effect.logError(`API Route Error: ${String(error)}`).pipe(
|
|
241
|
+
Effect.as(createJsonResponse({ error: 'Internal Server Error' }, { status: 500 }))
|
|
244
242
|
)
|
|
245
|
-
)
|
|
243
|
+
)
|
|
244
|
+
);
|
|
246
245
|
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
return response;
|
|
247
|
+
});
|
|
249
248
|
|
|
250
249
|
/**
|
|
251
250
|
* Creates a REST router that handles HTTP requests based on route type and optional ID parameters.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "studiocms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A Community-Driven Astro native CMS. Built from the ground up by the Astro community.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "withstudiocms",
|
|
@@ -222,25 +222,25 @@
|
|
|
222
222
|
"type": "module",
|
|
223
223
|
"dependencies": {
|
|
224
224
|
"@iconify-json/flat-color-icons": "^1.2.3",
|
|
225
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
225
|
+
"@iconify-json/simple-icons": "^1.2.66",
|
|
226
226
|
"@iconify-json/circle-flags": "^1.2.10",
|
|
227
227
|
"@inox-tools/runtime-logger": "^0.7.0",
|
|
228
228
|
"@nanostores/i18n": "^1.2.2",
|
|
229
229
|
"@nanostores/persistent": "^1.2.0",
|
|
230
230
|
"@outerbase/sdk-transform": "^1.0.8",
|
|
231
|
-
"@studiocms/ui": "^1.0.
|
|
232
|
-
"@withstudiocms/cli-kit": "^0.1.0",
|
|
231
|
+
"@studiocms/ui": "^1.0.1",
|
|
233
232
|
"ace-builds": "^1.43.5",
|
|
234
233
|
"astro-integration-kit": "^0.19.1",
|
|
235
234
|
"boxen": "^8.0.1",
|
|
236
235
|
"chalk": "^5.6.2",
|
|
237
|
-
"diff": "^8.0.
|
|
236
|
+
"diff": "^8.0.3",
|
|
238
237
|
"dompurify": "^3.3.1",
|
|
239
238
|
"dotenv": "^17.2.3",
|
|
240
239
|
"fuse.js": "^7.1.0",
|
|
241
240
|
"jose": "^6.1.3",
|
|
242
241
|
"micromark": "^4.0.2",
|
|
243
242
|
"micromark-extension-gfm": "^3.0.0",
|
|
243
|
+
"micromatch": "^4.0.8",
|
|
244
244
|
"magicast": "^0.5.1",
|
|
245
245
|
"mdast-util-to-markdown": "^2.1.2",
|
|
246
246
|
"mrmime": "^2.0.1",
|
|
@@ -251,31 +251,44 @@
|
|
|
251
251
|
"tinyglobby": "^0.2.15",
|
|
252
252
|
"ultrahtml": "^1.6.0",
|
|
253
253
|
"web-vitals": "^5.1.0",
|
|
254
|
-
"@withstudiocms/internal_helpers": "0.1.0",
|
|
255
|
-
"@withstudiocms/auth-kit": "0.1.
|
|
256
|
-
"@withstudiocms/
|
|
257
|
-
"@withstudiocms/
|
|
258
|
-
"@withstudiocms/
|
|
259
|
-
"@withstudiocms/
|
|
260
|
-
"@withstudiocms/kysely": "0.
|
|
261
|
-
"@withstudiocms/
|
|
254
|
+
"@withstudiocms/internal_helpers": "^0.1.0",
|
|
255
|
+
"@withstudiocms/auth-kit": "^0.1.2",
|
|
256
|
+
"@withstudiocms/config-utils": "^0.1.0",
|
|
257
|
+
"@withstudiocms/component-registry": "^0.1.2",
|
|
258
|
+
"@withstudiocms/cli-kit": "^0.2.0",
|
|
259
|
+
"@withstudiocms/effect": "^0.2.0",
|
|
260
|
+
"@withstudiocms/kysely": "^0.2.0",
|
|
261
|
+
"@withstudiocms/template-lang": "^0.1.0",
|
|
262
|
+
"@withstudiocms/sdk": "^0.2.0"
|
|
262
263
|
},
|
|
263
264
|
"devDependencies": {
|
|
264
265
|
"@types/mdast": "^4.0.4",
|
|
266
|
+
"@types/micromatch": "^4.0.10",
|
|
265
267
|
"@types/node": "^22.0.0",
|
|
266
268
|
"@types/semver": "^7.7.1",
|
|
267
269
|
"@types/three": "0.169.0",
|
|
268
270
|
"@types/pg": "^8.16.0",
|
|
269
|
-
"typescript": "^5.9.3"
|
|
271
|
+
"typescript": "^5.9.3",
|
|
272
|
+
"vite": "^6.3.4"
|
|
270
273
|
},
|
|
271
274
|
"peerDependencies": {
|
|
275
|
+
"@libsql/client": "^0.15.15",
|
|
272
276
|
"astro": "^5.12.9",
|
|
273
277
|
"effect": "^3.19.14",
|
|
274
|
-
"
|
|
275
|
-
"@libsql/client": "^0.15.15",
|
|
276
|
-
"pg": "^8.16.3",
|
|
278
|
+
"pg": "^8.17.1",
|
|
277
279
|
"mysql2": "^3.16.0"
|
|
278
280
|
},
|
|
281
|
+
"peerDependenciesMeta": {
|
|
282
|
+
"@libsql/client": {
|
|
283
|
+
"optional": true
|
|
284
|
+
},
|
|
285
|
+
"pg": {
|
|
286
|
+
"optional": true
|
|
287
|
+
},
|
|
288
|
+
"mysql2": {
|
|
289
|
+
"optional": true
|
|
290
|
+
}
|
|
291
|
+
},
|
|
279
292
|
"scripts": {
|
|
280
293
|
"build": "buildkit build 'src/**/*.{ts,astro,css,json,png,webp,woff2,stub.js,d.ts}' --tsconfig=tsconfig.build.json",
|
|
281
294
|
"dev": "buildkit dev 'src/**/*.{ts,astro,css,json,png,webp,woff2,stub.js,d.ts}'",
|