@orion-studios/cms 0.5.3 → 0.5.4
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/server/index.js +53 -28
- package/dist/studio/index.js +14 -8
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -389,7 +389,7 @@ var MIN_ROLE = {
|
|
|
389
389
|
"pages.delete": "admin",
|
|
390
390
|
"pages.restore": "editor",
|
|
391
391
|
"globals.read": "content",
|
|
392
|
-
"globals.write": "
|
|
392
|
+
"globals.write": "editor",
|
|
393
393
|
"media.read": "content",
|
|
394
394
|
"media.upload": "content",
|
|
395
395
|
"media.update": "content",
|
|
@@ -663,6 +663,8 @@ var errors = {
|
|
|
663
663
|
var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
664
664
|
var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
665
665
|
var PATH_PATTERN = /^\/(?:[a-z0-9-]+(?:\/[a-z0-9-]+)*)?$/;
|
|
666
|
+
var USER_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
667
|
+
var canonicalizeUserId = (value) => USER_ID_PATTERN.test(value) ? value.toLowerCase() : null;
|
|
666
668
|
var DEFAULT_MAX_UPLOAD_BYTES = 15 * 1024 * 1024;
|
|
667
669
|
var ALLOWED_UPLOAD_TYPES = /* @__PURE__ */ new Set([
|
|
668
670
|
"image/jpeg",
|
|
@@ -1706,23 +1708,30 @@ function createCmsRoutes(options) {
|
|
|
1706
1708
|
const listUsers = async (request) => {
|
|
1707
1709
|
const auth = await guard(request, "users.manage");
|
|
1708
1710
|
if (auth instanceof Response) return auth;
|
|
1709
|
-
const { data: authData, error: authError } = await db().auth.admin.listUsers({ perPage: 1e3 });
|
|
1710
|
-
if (authError) return errors.badRequest(authError.message);
|
|
1711
1711
|
const { data: profiles, error: profileError } = await db().from("cms_profiles").select("user_id, role, name");
|
|
1712
1712
|
if (profileError) return errors.badRequest(profileError.message);
|
|
1713
|
-
const
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1713
|
+
const profileById = /* @__PURE__ */ new Map();
|
|
1714
|
+
for (const profile of profiles || []) {
|
|
1715
|
+
const userId = typeof profile.user_id === "string" ? canonicalizeUserId(profile.user_id) : null;
|
|
1716
|
+
if (!userId || !isCmsRole(profile.role)) continue;
|
|
1717
|
+
profileById.set(userId, {
|
|
1718
|
+
role: profile.role,
|
|
1719
|
+
name: typeof profile.name === "string" ? profile.name : ""
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
const { data: authData, error: authError } = await db().auth.admin.listUsers({ perPage: 1e3 });
|
|
1723
|
+
if (authError) return errors.badRequest(authError.message);
|
|
1724
|
+
const users = authData.users.flatMap((user) => {
|
|
1725
|
+
const canonicalUserId = canonicalizeUserId(user.id);
|
|
1726
|
+
const profile = canonicalUserId ? profileById.get(canonicalUserId) : void 0;
|
|
1727
|
+
return profile ? [{
|
|
1719
1728
|
id: user.id,
|
|
1720
1729
|
email: user.email ?? null,
|
|
1721
|
-
name: profile
|
|
1722
|
-
role: profile
|
|
1730
|
+
name: profile.name,
|
|
1731
|
+
role: profile.role,
|
|
1723
1732
|
created_at: user.created_at ?? null,
|
|
1724
1733
|
last_sign_in_at: user.last_sign_in_at ?? null
|
|
1725
|
-
};
|
|
1734
|
+
}] : [];
|
|
1726
1735
|
});
|
|
1727
1736
|
return json({ users, assignableRoles: assignableRoles(auth.user.role) });
|
|
1728
1737
|
};
|
|
@@ -1757,20 +1766,31 @@ function createCmsRoutes(options) {
|
|
|
1757
1766
|
await logActivity(auth.user, "user.create", email);
|
|
1758
1767
|
return json({ user: { id: userId, email, name, role } }, 201);
|
|
1759
1768
|
};
|
|
1760
|
-
const
|
|
1761
|
-
const { data } = await db().from("cms_profiles").select("role").eq("user_id", userId).maybeSingle();
|
|
1762
|
-
return
|
|
1769
|
+
const loadMembership = async (userId) => {
|
|
1770
|
+
const { data, error } = await db().from("cms_profiles").select("role").eq("user_id", userId).maybeSingle();
|
|
1771
|
+
if (error) return { status: "query-error", message: error.message };
|
|
1772
|
+
if (!data) return { status: "missing" };
|
|
1773
|
+
if (!isCmsRole(data.role)) return { status: "invalid-role" };
|
|
1774
|
+
return { status: "valid", role: data.role };
|
|
1775
|
+
};
|
|
1776
|
+
const membershipError = (membership) => {
|
|
1777
|
+
if (membership.status === "missing") return errors.notFound();
|
|
1778
|
+
if (membership.status === "invalid-role") return errors.badRequest("Invalid CMS profile role.");
|
|
1779
|
+
return errors.badRequest(membership.message);
|
|
1763
1780
|
};
|
|
1764
1781
|
const updateUser = async (request, userId) => {
|
|
1765
1782
|
const auth = await guard(request, "users.manage");
|
|
1766
1783
|
if (auth instanceof Response) return auth;
|
|
1784
|
+
const canonicalUserId = canonicalizeUserId(userId);
|
|
1785
|
+
if (!canonicalUserId) return errors.badRequest("Invalid user ID.");
|
|
1767
1786
|
const body = await readJson(request);
|
|
1768
1787
|
if (!body) return errors.badRequest("Invalid body.");
|
|
1769
|
-
const
|
|
1770
|
-
if (
|
|
1788
|
+
const membership = await loadMembership(canonicalUserId);
|
|
1789
|
+
if (membership.status !== "valid") return membershipError(membership);
|
|
1790
|
+
if (!outranksOrEqual(auth.user.role, membership.role)) return errors.forbidden();
|
|
1771
1791
|
if (body.role !== void 0) {
|
|
1772
1792
|
if (!isCmsRole(body.role)) return errors.badRequest("Invalid role.");
|
|
1773
|
-
if (
|
|
1793
|
+
if (canonicalUserId === auth.user.id && body.role !== auth.user.role) {
|
|
1774
1794
|
return errors.badRequest("You can't change your own role.");
|
|
1775
1795
|
}
|
|
1776
1796
|
if (!outranksOrEqual(auth.user.role, body.role)) {
|
|
@@ -1779,32 +1799,37 @@ function createCmsRoutes(options) {
|
|
|
1779
1799
|
}
|
|
1780
1800
|
if (typeof body.password === "string") {
|
|
1781
1801
|
if (body.password.length < 8) return errors.badRequest("Password must be at least 8 characters.");
|
|
1782
|
-
const { error: passwordError } = await db().auth.admin.updateUserById(
|
|
1802
|
+
const { error: passwordError } = await db().auth.admin.updateUserById(canonicalUserId, {
|
|
1783
1803
|
password: body.password
|
|
1784
1804
|
});
|
|
1785
1805
|
if (passwordError) return errors.badRequest(passwordError.message);
|
|
1786
1806
|
}
|
|
1787
1807
|
if (body.role !== void 0 || typeof body.name === "string") {
|
|
1788
|
-
const patch = {
|
|
1808
|
+
const patch = {};
|
|
1789
1809
|
if (body.role !== void 0) patch.role = body.role;
|
|
1790
1810
|
if (typeof body.name === "string") patch.name = body.name.trim();
|
|
1791
|
-
const { error: profileError } = await db().from("cms_profiles").
|
|
1811
|
+
const { error: profileError } = await db().from("cms_profiles").update(patch).eq("user_id", canonicalUserId);
|
|
1792
1812
|
if (profileError) return errors.badRequest(profileError.message);
|
|
1793
1813
|
}
|
|
1794
|
-
await logActivity(auth.user, "user.update",
|
|
1814
|
+
await logActivity(auth.user, "user.update", canonicalUserId);
|
|
1795
1815
|
return json({ success: true });
|
|
1796
1816
|
};
|
|
1797
1817
|
const deleteUser = async (request, userId) => {
|
|
1798
1818
|
const auth = await guard(request, "users.manage");
|
|
1799
1819
|
if (auth instanceof Response) return auth;
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1820
|
+
const canonicalUserId = canonicalizeUserId(userId);
|
|
1821
|
+
if (!canonicalUserId) return errors.badRequest("Invalid user ID.");
|
|
1822
|
+
const membership = await loadMembership(canonicalUserId);
|
|
1823
|
+
if (membership.status !== "valid") return membershipError(membership);
|
|
1824
|
+
if (canonicalUserId === auth.user.id) {
|
|
1825
|
+
return errors.badRequest("You can't remove your own account.");
|
|
1826
|
+
}
|
|
1827
|
+
if (!outranksOrEqual(auth.user.role, membership.role)) return errors.forbidden();
|
|
1828
|
+
const { error: authError } = await db().auth.admin.deleteUser(canonicalUserId);
|
|
1804
1829
|
if (authError) return errors.badRequest(authError.message);
|
|
1805
|
-
const { error: profileError } = await db().from("cms_profiles").delete().eq("user_id",
|
|
1830
|
+
const { error: profileError } = await db().from("cms_profiles").delete().eq("user_id", canonicalUserId);
|
|
1806
1831
|
if (profileError) return errors.badRequest(profileError.message);
|
|
1807
|
-
await logActivity(auth.user, "user.delete",
|
|
1832
|
+
await logActivity(auth.user, "user.delete", canonicalUserId);
|
|
1808
1833
|
return json({ success: true });
|
|
1809
1834
|
};
|
|
1810
1835
|
const runSync = async (request) => {
|
package/dist/studio/index.js
CHANGED
|
@@ -149,7 +149,7 @@ var MIN_ROLE = {
|
|
|
149
149
|
"pages.delete": "admin",
|
|
150
150
|
"pages.restore": "editor",
|
|
151
151
|
"globals.read": "content",
|
|
152
|
-
"globals.write": "
|
|
152
|
+
"globals.write": "editor",
|
|
153
153
|
"media.read": "content",
|
|
154
154
|
"media.upload": "content",
|
|
155
155
|
"media.update": "content",
|
|
@@ -179,13 +179,14 @@ var SECTION_ACTION = {
|
|
|
179
179
|
forms: "forms.read",
|
|
180
180
|
submissions: "submissions.read",
|
|
181
181
|
analytics: "analytics.read",
|
|
182
|
-
globals: "globals.
|
|
182
|
+
globals: "globals.write",
|
|
183
183
|
redirects: "redirects.manage",
|
|
184
184
|
users: "users.manage"
|
|
185
185
|
};
|
|
186
186
|
var userForRole = (role) => ({ id: "", email: null, name: "", role });
|
|
187
187
|
var studioCan = (role, action) => can(userForRole(role), action);
|
|
188
188
|
var canViewStudioSection = (role, section) => studioCan(role, SECTION_ACTION[section]);
|
|
189
|
+
var resolveStudioSection = (role, section) => role && canViewStudioSection(role, section) ? section : "dashboard";
|
|
189
190
|
var studioAccess = (role) => ({
|
|
190
191
|
changePageStructure: studioCan(role, "pages.changeStructure"),
|
|
191
192
|
publishPages: studioCan(role, "pages.publish"),
|
|
@@ -3728,11 +3729,15 @@ function Studio({ registry, globals, siteName, logoUrl, supabaseUrl }) {
|
|
|
3728
3729
|
useEffect11(() => {
|
|
3729
3730
|
if (!session) {
|
|
3730
3731
|
setRole(null);
|
|
3732
|
+
setSection("dashboard");
|
|
3733
|
+
setOpenPageId(null);
|
|
3734
|
+
setSubmissionsFormId(null);
|
|
3731
3735
|
return;
|
|
3732
3736
|
}
|
|
3733
3737
|
api.me().then(
|
|
3734
3738
|
({ user }) => {
|
|
3735
3739
|
setRole(user.role);
|
|
3740
|
+
setSection((currentSection) => resolveStudioSection(user.role, currentSection));
|
|
3736
3741
|
refreshMedia();
|
|
3737
3742
|
refreshUnread();
|
|
3738
3743
|
refreshPages();
|
|
@@ -3784,6 +3789,7 @@ function Studio({ registry, globals, siteName, logoUrl, supabaseUrl }) {
|
|
|
3784
3789
|
if (!role) return /* @__PURE__ */ jsx12("div", { className: "ost-root ost-loading", children: "Loading Studio\u2026" });
|
|
3785
3790
|
const access = studioAccess(role);
|
|
3786
3791
|
const nav = NAV.filter((item) => canViewStudioSection(role, item.key));
|
|
3792
|
+
const selectedSection = resolveStudioSection(role, section);
|
|
3787
3793
|
const goTo = (key) => {
|
|
3788
3794
|
setSection(key);
|
|
3789
3795
|
setOpenPageId(null);
|
|
@@ -3806,7 +3812,7 @@ function Studio({ registry, globals, siteName, logoUrl, supabaseUrl }) {
|
|
|
3806
3812
|
nav.map((item) => /* @__PURE__ */ jsxs12(
|
|
3807
3813
|
"button",
|
|
3808
3814
|
{
|
|
3809
|
-
className: `ost-nav-item${
|
|
3815
|
+
className: `ost-nav-item${selectedSection === item.key && !openPageId ? " is-active" : ""}`,
|
|
3810
3816
|
onClick: () => goTo(item.key),
|
|
3811
3817
|
type: "button",
|
|
3812
3818
|
children: [
|
|
@@ -3841,7 +3847,7 @@ function Studio({ registry, globals, siteName, logoUrl, supabaseUrl }) {
|
|
|
3841
3847
|
role
|
|
3842
3848
|
},
|
|
3843
3849
|
openPageId
|
|
3844
|
-
) :
|
|
3850
|
+
) : selectedSection === "pages" ? /* @__PURE__ */ jsx12(PagesView, { api, canCreate: access.createPages, onOpen: openPage }) : selectedSection === "media" ? /* @__PURE__ */ jsx12(
|
|
3845
3851
|
MediaView,
|
|
3846
3852
|
{
|
|
3847
3853
|
api,
|
|
@@ -3852,7 +3858,7 @@ function Studio({ registry, globals, siteName, logoUrl, supabaseUrl }) {
|
|
|
3852
3858
|
onChanged: refreshMedia,
|
|
3853
3859
|
onUpload: uploadMedia
|
|
3854
3860
|
}
|
|
3855
|
-
) :
|
|
3861
|
+
) : selectedSection === "forms" ? /* @__PURE__ */ jsx12(
|
|
3856
3862
|
FormsView,
|
|
3857
3863
|
{
|
|
3858
3864
|
api,
|
|
@@ -3864,7 +3870,7 @@ function Studio({ registry, globals, siteName, logoUrl, supabaseUrl }) {
|
|
|
3864
3870
|
setSection("submissions");
|
|
3865
3871
|
}
|
|
3866
3872
|
}
|
|
3867
|
-
) :
|
|
3873
|
+
) : selectedSection === "submissions" && access.readSubmissions ? /* @__PURE__ */ jsx12(
|
|
3868
3874
|
SubmissionsView,
|
|
3869
3875
|
{
|
|
3870
3876
|
api,
|
|
@@ -3873,7 +3879,7 @@ function Studio({ registry, globals, siteName, logoUrl, supabaseUrl }) {
|
|
|
3873
3879
|
onUnreadChanged: refreshUnread
|
|
3874
3880
|
},
|
|
3875
3881
|
submissionsFormId || "all"
|
|
3876
|
-
) :
|
|
3882
|
+
) : selectedSection === "analytics" && access.readAnalytics ? /* @__PURE__ */ jsx12(AnalyticsView, { api }) : selectedSection === "globals" ? /* @__PURE__ */ jsx12(
|
|
3877
3883
|
GlobalsView,
|
|
3878
3884
|
{
|
|
3879
3885
|
api,
|
|
@@ -3883,7 +3889,7 @@ function Studio({ registry, globals, siteName, logoUrl, supabaseUrl }) {
|
|
|
3883
3889
|
onUploadMedia: uploadMedia,
|
|
3884
3890
|
pages
|
|
3885
3891
|
}
|
|
3886
|
-
) :
|
|
3892
|
+
) : selectedSection === "redirects" && access.manageRedirects ? /* @__PURE__ */ jsx12(RedirectsView, { api }) : selectedSection === "users" && access.manageUsers ? /* @__PURE__ */ jsx12(UsersView, { api, meId: session.user.id }) : /* @__PURE__ */ jsx12(
|
|
3887
3893
|
DashboardView,
|
|
3888
3894
|
{
|
|
3889
3895
|
api,
|