@aphexcms/cms-core 0.1.0 → 0.1.3
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/package.json +22 -5
- package/src/api/assets.ts +0 -75
- package/src/api/client.ts +0 -150
- package/src/api/documents.ts +0 -102
- package/src/api/index.ts +0 -7
- package/src/api/organizations.ts +0 -154
- package/src/api/types.ts +0 -34
- package/src/app.d.ts +0 -19
- package/src/auth/MULTI_TENANCY_PLAN.md +0 -1183
- package/src/auth/auth-errors.ts +0 -23
- package/src/auth/auth-hooks.ts +0 -132
- package/src/auth/provider.ts +0 -25
- package/src/client/index.ts +0 -47
- package/src/components/AdminApp.svelte +0 -1078
- package/src/components/admin/AdminLayout.svelte +0 -115
- package/src/components/admin/DocumentEditor.svelte +0 -795
- package/src/components/admin/DocumentTypesList.svelte +0 -97
- package/src/components/admin/ObjectModal.svelte +0 -135
- package/src/components/admin/SchemaField.svelte +0 -171
- package/src/components/admin/fields/ArrayField.svelte +0 -266
- package/src/components/admin/fields/BooleanField.svelte +0 -35
- package/src/components/admin/fields/ImageField.svelte +0 -284
- package/src/components/admin/fields/NumberField.svelte +0 -82
- package/src/components/admin/fields/ReferenceField.svelte +0 -260
- package/src/components/admin/fields/SlugField.svelte +0 -74
- package/src/components/admin/fields/StringField.svelte +0 -40
- package/src/components/admin/fields/TextareaField.svelte +0 -40
- package/src/components/fields/index.ts +0 -9
- package/src/components/index.ts +0 -16
- package/src/components/layout/OrganizationSwitcher.svelte +0 -218
- package/src/components/layout/Sidebar.svelte +0 -88
- package/src/components/layout/sidebar/AppSidebar.svelte +0 -63
- package/src/components/layout/sidebar/NavMain.svelte +0 -95
- package/src/components/layout/sidebar/NavSecondary.svelte +0 -69
- package/src/components/layout/sidebar/NavUser.svelte +0 -85
- package/src/config.ts +0 -18
- package/src/db/adapters/index.ts +0 -3
- package/src/db/index.ts +0 -5
- package/src/db/interfaces/asset.ts +0 -61
- package/src/db/interfaces/document.ts +0 -53
- package/src/db/interfaces/index.ts +0 -98
- package/src/db/interfaces/organization.ts +0 -51
- package/src/db/interfaces/schema.ts +0 -13
- package/src/db/interfaces/user.ts +0 -16
- package/src/db/utils/reference-resolver.ts +0 -119
- package/src/define.ts +0 -7
- package/src/email/index.ts +0 -5
- package/src/email/interfaces/email.ts +0 -45
- package/src/engine.ts +0 -85
- package/src/field-validation/rule.ts +0 -287
- package/src/field-validation/utils.ts +0 -91
- package/src/hooks.ts +0 -142
- package/src/index.ts +0 -5
- package/src/lib/is-mobile.svelte.ts +0 -9
- package/src/lib/utils.ts +0 -13
- package/src/plugins/README.md +0 -154
- package/src/routes/assets-by-id.ts +0 -161
- package/src/routes/assets-cdn.ts +0 -185
- package/src/routes/assets.ts +0 -116
- package/src/routes/documents-by-id.ts +0 -188
- package/src/routes/documents-publish.ts +0 -211
- package/src/routes/documents.ts +0 -172
- package/src/routes/index.ts +0 -13
- package/src/routes/organizations-by-id.ts +0 -258
- package/src/routes/organizations-invitations.ts +0 -183
- package/src/routes/organizations-members.ts +0 -301
- package/src/routes/organizations-switch.ts +0 -74
- package/src/routes/organizations.ts +0 -146
- package/src/routes/schemas-by-type.ts +0 -35
- package/src/routes/schemas.ts +0 -19
- package/src/routes-exports.ts +0 -42
- package/src/schema-context.svelte.ts +0 -24
- package/src/schema-utils/cleanup.ts +0 -116
- package/src/schema-utils/index.ts +0 -4
- package/src/schema-utils/utils.ts +0 -47
- package/src/schema-utils/validator.ts +0 -58
- package/src/server/index.ts +0 -40
- package/src/services/asset-service.ts +0 -256
- package/src/services/index.ts +0 -6
- package/src/storage/adapters/index.ts +0 -2
- package/src/storage/adapters/local-storage-adapter.ts +0 -215
- package/src/storage/index.ts +0 -8
- package/src/storage/interfaces/index.ts +0 -2
- package/src/storage/interfaces/storage.ts +0 -114
- package/src/storage/providers/storage.ts +0 -83
- package/src/types/asset.ts +0 -81
- package/src/types/auth.ts +0 -80
- package/src/types/config.ts +0 -45
- package/src/types/document.ts +0 -38
- package/src/types/index.ts +0 -8
- package/src/types/organization.ts +0 -119
- package/src/types/schemas.ts +0 -151
- package/src/types/sidebar.ts +0 -37
- package/src/types/user.ts +0 -17
- package/src/utils/content-hash.ts +0 -75
- package/src/utils/image-url.ts +0 -204
- package/src/utils/index.ts +0 -12
- package/src/utils/slug.ts +0 -33
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// Organization types for multi-tenancy
|
|
2
|
-
// These match the inferred types from Drizzle schema
|
|
3
|
-
|
|
4
|
-
export type OrganizationRole = 'owner' | 'admin' | 'editor' | 'viewer';
|
|
5
|
-
|
|
6
|
-
export interface Organization {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
slug: string;
|
|
10
|
-
parentOrganizationId: string | null;
|
|
11
|
-
metadata: {
|
|
12
|
-
logo?: string;
|
|
13
|
-
theme?: {
|
|
14
|
-
primaryColor: string;
|
|
15
|
-
fontFamily: string;
|
|
16
|
-
logoUrl: string;
|
|
17
|
-
};
|
|
18
|
-
website?: string;
|
|
19
|
-
settings?: Record<string, any>;
|
|
20
|
-
} | null;
|
|
21
|
-
createdBy: string;
|
|
22
|
-
createdAt: Date;
|
|
23
|
-
updatedAt: Date;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface NewOrganization {
|
|
27
|
-
name: string;
|
|
28
|
-
slug: string;
|
|
29
|
-
createdBy: string;
|
|
30
|
-
id?: string;
|
|
31
|
-
parentOrganizationId?: string | null;
|
|
32
|
-
metadata?: {
|
|
33
|
-
logo?: string;
|
|
34
|
-
theme?: {
|
|
35
|
-
primaryColor: string;
|
|
36
|
-
fontFamily: string;
|
|
37
|
-
logoUrl: string;
|
|
38
|
-
};
|
|
39
|
-
website?: string;
|
|
40
|
-
settings?: Record<string, any>;
|
|
41
|
-
} | null;
|
|
42
|
-
createdAt?: Date;
|
|
43
|
-
updatedAt?: Date;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface OrganizationMember {
|
|
47
|
-
id: string;
|
|
48
|
-
createdAt: Date;
|
|
49
|
-
updatedAt: Date;
|
|
50
|
-
organizationId: string;
|
|
51
|
-
userId: string;
|
|
52
|
-
role: OrganizationRole;
|
|
53
|
-
preferences: Record<string, any> | null;
|
|
54
|
-
invitationId: string | null; // Link to invitation (get invitedBy, invitedEmail from there)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface NewOrganizationMember {
|
|
58
|
-
organizationId: string;
|
|
59
|
-
userId: string;
|
|
60
|
-
role: OrganizationRole;
|
|
61
|
-
id?: string;
|
|
62
|
-
createdAt?: Date;
|
|
63
|
-
updatedAt?: Date;
|
|
64
|
-
preferences?: Record<string, any> | null;
|
|
65
|
-
invitationId?: string | null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface Invitation {
|
|
69
|
-
id: string;
|
|
70
|
-
createdAt: Date;
|
|
71
|
-
organizationId: string;
|
|
72
|
-
role: OrganizationRole;
|
|
73
|
-
invitedBy: string;
|
|
74
|
-
email: string;
|
|
75
|
-
token: string;
|
|
76
|
-
expiresAt: Date;
|
|
77
|
-
acceptedAt: Date | null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface NewInvitation {
|
|
81
|
-
organizationId: string;
|
|
82
|
-
role: OrganizationRole;
|
|
83
|
-
invitedBy: string;
|
|
84
|
-
email: string;
|
|
85
|
-
token: string;
|
|
86
|
-
expiresAt: Date;
|
|
87
|
-
id?: string;
|
|
88
|
-
createdAt?: Date;
|
|
89
|
-
acceptedAt?: Date | null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface UserSession {
|
|
93
|
-
updatedAt: Date;
|
|
94
|
-
userId: string;
|
|
95
|
-
activeOrganizationId: string | null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface NewUserSession {
|
|
99
|
-
userId: string;
|
|
100
|
-
updatedAt?: Date;
|
|
101
|
-
activeOrganizationId?: string | null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Helper type for organization with member info
|
|
105
|
-
export interface OrganizationMembership {
|
|
106
|
-
organization: Organization;
|
|
107
|
-
member: OrganizationMember;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Helper type for member with user info
|
|
111
|
-
export interface OrganizationMemberWithUser {
|
|
112
|
-
member: OrganizationMember;
|
|
113
|
-
user: {
|
|
114
|
-
id: string;
|
|
115
|
-
email: string;
|
|
116
|
-
name: string | null;
|
|
117
|
-
image: string | null;
|
|
118
|
-
};
|
|
119
|
-
}
|
package/src/types/schemas.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
// types/schemas.ts
|
|
2
|
-
import type { Rule } from '../field-validation/rule.js';
|
|
3
|
-
|
|
4
|
-
// From root types.ts
|
|
5
|
-
export type FieldType =
|
|
6
|
-
| 'string'
|
|
7
|
-
| 'text'
|
|
8
|
-
| 'number'
|
|
9
|
-
| 'boolean'
|
|
10
|
-
| 'slug'
|
|
11
|
-
| 'image'
|
|
12
|
-
| 'array'
|
|
13
|
-
| 'object'
|
|
14
|
-
| 'reference';
|
|
15
|
-
|
|
16
|
-
export interface BaseField {
|
|
17
|
-
name: string;
|
|
18
|
-
type: FieldType;
|
|
19
|
-
title: string;
|
|
20
|
-
description?: string;
|
|
21
|
-
validation?: ((rule: Rule) => Rule) | Array<(rule: Rule) => Rule>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface StringField extends BaseField {
|
|
25
|
-
type: 'string';
|
|
26
|
-
maxLength?: number;
|
|
27
|
-
placeholder?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface TextField extends BaseField {
|
|
31
|
-
type: 'text';
|
|
32
|
-
rows?: number;
|
|
33
|
-
maxLength?: number;
|
|
34
|
-
placeholder?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface NumberField extends BaseField {
|
|
38
|
-
type: 'number';
|
|
39
|
-
min?: number;
|
|
40
|
-
max?: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface BooleanField extends BaseField {
|
|
44
|
-
type: 'boolean';
|
|
45
|
-
initialValue?: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface SlugField extends BaseField {
|
|
49
|
-
type: 'slug';
|
|
50
|
-
source?: string;
|
|
51
|
-
maxLength?: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface ImageField extends BaseField {
|
|
55
|
-
type: 'image';
|
|
56
|
-
accept?: string;
|
|
57
|
-
hotspot?: boolean; // Enable hotspot/crop UI
|
|
58
|
-
metadata?: string[]; // e.g., ['palette', 'exif', 'location']
|
|
59
|
-
fields?: Field[]; // Additional fields like caption, attribution
|
|
60
|
-
private?: boolean; // Default: false (public). Set true to require auth for access
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface TypeReference {
|
|
64
|
-
type: string; // References a SchemaType by name
|
|
65
|
-
title?: string;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface ArrayField extends BaseField {
|
|
69
|
-
type: 'array';
|
|
70
|
-
of: TypeReference[];
|
|
71
|
-
max?: number;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface ObjectField extends BaseField {
|
|
75
|
-
type: 'object';
|
|
76
|
-
fields: Field[];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface ReferenceField extends BaseField {
|
|
80
|
-
type: 'reference';
|
|
81
|
-
to: Array<{ type: string }>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type Field =
|
|
85
|
-
| StringField
|
|
86
|
-
| TextField
|
|
87
|
-
| NumberField
|
|
88
|
-
| BooleanField
|
|
89
|
-
| SlugField
|
|
90
|
-
| ImageField
|
|
91
|
-
| ArrayField
|
|
92
|
-
| ObjectField
|
|
93
|
-
| ReferenceField;
|
|
94
|
-
|
|
95
|
-
export interface PreviewConfig {
|
|
96
|
-
select?: {
|
|
97
|
-
title?: string;
|
|
98
|
-
subtitle?: string;
|
|
99
|
-
media?: string;
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface DocumentType {
|
|
104
|
-
id: string;
|
|
105
|
-
type: 'document';
|
|
106
|
-
name: string;
|
|
107
|
-
title: string;
|
|
108
|
-
description?: string;
|
|
109
|
-
fields: Field[];
|
|
110
|
-
preview?: PreviewConfig;
|
|
111
|
-
createdAt: Date | null;
|
|
112
|
-
updatedAt: Date | null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export interface ObjectType {
|
|
116
|
-
type: 'object';
|
|
117
|
-
name: string;
|
|
118
|
-
title: string;
|
|
119
|
-
description?: string;
|
|
120
|
-
fields: Field[];
|
|
121
|
-
preview?: PreviewConfig;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// From db/types.ts
|
|
125
|
-
/**
|
|
126
|
-
* Schema type - represents document and object type definitions stored in the DB
|
|
127
|
-
*/
|
|
128
|
-
// Schema type for all definitions
|
|
129
|
-
export interface SchemaType {
|
|
130
|
-
id?: string;
|
|
131
|
-
type: 'document' | 'object';
|
|
132
|
-
name: string;
|
|
133
|
-
title: string;
|
|
134
|
-
description?: string;
|
|
135
|
-
fields: Field[];
|
|
136
|
-
preview?: PreviewConfig;
|
|
137
|
-
createdAt?: Date | null;
|
|
138
|
-
updatedAt?: Date | null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export interface NewSchemaType {
|
|
142
|
-
id?: string;
|
|
143
|
-
type: 'document' | 'object';
|
|
144
|
-
name: string;
|
|
145
|
-
title: string;
|
|
146
|
-
description?: string;
|
|
147
|
-
fields: Field[];
|
|
148
|
-
preview?: PreviewConfig;
|
|
149
|
-
createdAt?: Date | null;
|
|
150
|
-
updatedAt?: Date | null;
|
|
151
|
-
}
|
package/src/types/sidebar.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// Sidebar data types - extensible interfaces for admin UI
|
|
2
|
-
export interface SidebarUser {
|
|
3
|
-
id: string;
|
|
4
|
-
email: string;
|
|
5
|
-
name?: string;
|
|
6
|
-
image?: string;
|
|
7
|
-
role?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface SidebarNavItem {
|
|
11
|
-
href: string;
|
|
12
|
-
label: string;
|
|
13
|
-
icon?: string;
|
|
14
|
-
badge?: string | number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface SidebarBranding {
|
|
18
|
-
title?: string;
|
|
19
|
-
logo?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface SidebarOrganization {
|
|
23
|
-
id: string;
|
|
24
|
-
name: string;
|
|
25
|
-
slug: string;
|
|
26
|
-
role: 'owner' | 'admin' | 'editor' | 'viewer';
|
|
27
|
-
isActive: boolean;
|
|
28
|
-
metadata?: any;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface SidebarData {
|
|
32
|
-
user: SidebarUser;
|
|
33
|
-
branding?: SidebarBranding;
|
|
34
|
-
navItems?: SidebarNavItem[]; // Optional custom nav items (defaults to Content)
|
|
35
|
-
organizations?: SidebarOrganization[]; // User's organizations for organization switcher
|
|
36
|
-
activeOrganization?: SidebarOrganization; // Currently active organization
|
|
37
|
-
}
|
package/src/types/user.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// Represents the data stored in the cms_user_profiles table.
|
|
2
|
-
export interface UserProfile {
|
|
3
|
-
userId: string;
|
|
4
|
-
role: 'super_admin' | 'admin' | 'editor' | 'viewer';
|
|
5
|
-
preferences?: Record<string, any>;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// Represents the core user data from the auth provider.
|
|
9
|
-
export interface AuthUser {
|
|
10
|
-
id: string;
|
|
11
|
-
email: string;
|
|
12
|
-
name?: string;
|
|
13
|
-
image?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// The final, combined object, composed from the two types above.
|
|
17
|
-
export interface CMSUser extends AuthUser, Omit<UserProfile, 'userId'> {}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Content hashing utilities for document version tracking
|
|
3
|
-
* Includes timestamp for proper change detection and UX
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Recursively sort object keys for stable JSON serialization
|
|
8
|
-
*/
|
|
9
|
-
function sortObject(item: any): any {
|
|
10
|
-
if (item === null || typeof item !== 'object') return item;
|
|
11
|
-
|
|
12
|
-
if (Array.isArray(item)) {
|
|
13
|
-
return item.map(sortObject);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const sortedKeys = Object.keys(item).sort();
|
|
17
|
-
const sortedObj: any = {};
|
|
18
|
-
for (const key of sortedKeys) {
|
|
19
|
-
sortedObj[key] = sortObject(item[key]);
|
|
20
|
-
}
|
|
21
|
-
return sortedObj;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Create a stable hash from any object using a simple hash algorithm
|
|
26
|
-
*/
|
|
27
|
-
function simpleHash(str: string): string {
|
|
28
|
-
let hash = 0;
|
|
29
|
-
for (let i = 0; i < str.length; i++) {
|
|
30
|
-
const char = str.charCodeAt(i);
|
|
31
|
-
hash = (hash << 5) - hash + char;
|
|
32
|
-
hash = hash & hash; // Convert to 32-bit integer
|
|
33
|
-
}
|
|
34
|
-
return Math.abs(hash).toString(36); // Base36 for shorter string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Create a content hash including timestamp for change tracking
|
|
39
|
-
* This matches Sanity's behavior where any interaction creates a publishable state
|
|
40
|
-
*/
|
|
41
|
-
export function createContentHash(data: any, includeTimestamp = true): string {
|
|
42
|
-
const hashData = includeTimestamp
|
|
43
|
-
? {
|
|
44
|
-
...data,
|
|
45
|
-
_lastModified: new Date().toISOString()
|
|
46
|
-
}
|
|
47
|
-
: data;
|
|
48
|
-
|
|
49
|
-
const stableJson = JSON.stringify(sortObject(hashData));
|
|
50
|
-
return simpleHash(stableJson);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Create a hash from published data (no timestamp needed as it's already stable)
|
|
55
|
-
*/
|
|
56
|
-
export function createPublishedHash(data: any): string {
|
|
57
|
-
return createContentHash(data, false);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Compare if current draft differs from published version
|
|
62
|
-
*/
|
|
63
|
-
export function hasUnpublishedChanges(draftData: any, publishedHash: string | null): boolean {
|
|
64
|
-
if (!publishedHash) return true; // No published version = has changes
|
|
65
|
-
|
|
66
|
-
const publishedDataHash = createPublishedHash(draftData);
|
|
67
|
-
return publishedDataHash !== publishedHash;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Utility to create a clean published hash when publishing
|
|
72
|
-
*/
|
|
73
|
-
export function createHashForPublishing(draftData: any): string {
|
|
74
|
-
return createPublishedHash(draftData);
|
|
75
|
-
}
|
package/src/utils/image-url.ts
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import type { ImageValue, ImageAsset, Asset } from '../types/asset.js';
|
|
2
|
-
|
|
3
|
-
export interface ImageUrlBuilderOptions {
|
|
4
|
-
width?: number;
|
|
5
|
-
height?: number;
|
|
6
|
-
quality?: number;
|
|
7
|
-
format?: 'jpg' | 'jpeg' | 'png' | 'webp' | 'avif';
|
|
8
|
-
fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
9
|
-
auto?: 'format';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface ImageUrlBuilderConfig {
|
|
13
|
-
baseUrl?: string;
|
|
14
|
-
/**
|
|
15
|
-
* Function to sign asset URLs for secure, time-limited access
|
|
16
|
-
* Used for multi-tenant access without exposing API keys
|
|
17
|
-
*/
|
|
18
|
-
signAssetUrl?: (assetId: string) => string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Helper to extract URL from various image source types
|
|
23
|
-
* Works with GraphQL responses that include resolved asset data
|
|
24
|
-
*/
|
|
25
|
-
function extractUrl(source: any): string | null {
|
|
26
|
-
if (!source) return null;
|
|
27
|
-
|
|
28
|
-
// Direct URL string
|
|
29
|
-
if (typeof source === 'string') {
|
|
30
|
-
return source;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Asset object with url property
|
|
34
|
-
if (typeof source === 'object' && 'url' in source && source.url) {
|
|
35
|
-
return source.url;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ImageValue with resolved asset (from GraphQL)
|
|
39
|
-
if (typeof source === 'object' && 'asset' in source && source.asset) {
|
|
40
|
-
// Check if asset is resolved (has url property)
|
|
41
|
-
if (typeof source.asset === 'object' && 'url' in source.asset) {
|
|
42
|
-
return source.asset.url;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class ImageUrlBuilder {
|
|
50
|
-
private _source: any = null;
|
|
51
|
-
private _options: ImageUrlBuilderOptions = {};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Set the image source
|
|
55
|
-
*/
|
|
56
|
-
image(source: ImageValue | ImageAsset | string | Asset | null | undefined): this {
|
|
57
|
-
this._source = source;
|
|
58
|
-
return this;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Set width (for future dynamic image rendering)
|
|
63
|
-
*/
|
|
64
|
-
width(width: number): this {
|
|
65
|
-
this._options.width = width;
|
|
66
|
-
return this;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Set height (for future dynamic image rendering)
|
|
71
|
-
*/
|
|
72
|
-
height(height: number): this {
|
|
73
|
-
this._options.height = height;
|
|
74
|
-
return this;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Set both width and height (for future dynamic image rendering)
|
|
79
|
-
*/
|
|
80
|
-
size(width: number, height: number): this {
|
|
81
|
-
this._options.width = width;
|
|
82
|
-
this._options.height = height;
|
|
83
|
-
return this;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Set quality (for future dynamic image rendering)
|
|
88
|
-
*/
|
|
89
|
-
quality(quality: number): this {
|
|
90
|
-
this._options.quality = Math.max(1, Math.min(100, quality));
|
|
91
|
-
return this;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Set format (for future dynamic image rendering)
|
|
96
|
-
*/
|
|
97
|
-
format(format: 'jpg' | 'jpeg' | 'png' | 'webp' | 'avif'): this {
|
|
98
|
-
this._options.format = format;
|
|
99
|
-
return this;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Set fit mode (for future dynamic image rendering)
|
|
104
|
-
*/
|
|
105
|
-
fit(fit: 'cover' | 'contain' | 'fill' | 'inside' | 'outside'): this {
|
|
106
|
-
this._options.fit = fit;
|
|
107
|
-
return this;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Enable automatic format selection (for future dynamic image rendering)
|
|
112
|
-
*/
|
|
113
|
-
auto(mode: 'format'): this {
|
|
114
|
-
this._options.auto = mode;
|
|
115
|
-
return this;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Build the final URL
|
|
120
|
-
* Returns /api/assets/{id}?dl=1 which redirects to the actual S3/R2 URL
|
|
121
|
-
* Transformations (.width(), .quality(), etc) are stored but not yet applied
|
|
122
|
-
*
|
|
123
|
-
* For multi-tenant access, use signAssetUrl config to generate signed URLs
|
|
124
|
-
* TODO: Add dynamic image rendering support
|
|
125
|
-
*/
|
|
126
|
-
url(): string | null {
|
|
127
|
-
console.log('[ImageUrlBuilder] url() called with source:', JSON.stringify(this._source));
|
|
128
|
-
|
|
129
|
-
if (!this._source) {
|
|
130
|
-
console.log('[ImageUrlBuilder] No source provided');
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// First try to extract a direct URL (if asset was already resolved)
|
|
135
|
-
const directUrl = extractUrl(this._source);
|
|
136
|
-
if (directUrl) {
|
|
137
|
-
console.log('[ImageUrlBuilder] Using direct URL from resolved asset:', directUrl);
|
|
138
|
-
return directUrl;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Otherwise, build an API URL from the asset reference
|
|
142
|
-
let assetId: string | null = null;
|
|
143
|
-
|
|
144
|
-
if (typeof this._source === 'string') {
|
|
145
|
-
console.log('[ImageUrlBuilder] Source is string:', this._source);
|
|
146
|
-
assetId = this._source;
|
|
147
|
-
} else if (typeof this._source === 'object') {
|
|
148
|
-
console.log('[ImageUrlBuilder] Source is object, checking for asset._ref or _ref');
|
|
149
|
-
if ('asset' in this._source && this._source.asset?._ref) {
|
|
150
|
-
assetId = this._source.asset._ref;
|
|
151
|
-
console.log('[ImageUrlBuilder] Found asset._ref:', assetId);
|
|
152
|
-
} else if ('_ref' in this._source) {
|
|
153
|
-
assetId = this._source._ref;
|
|
154
|
-
console.log('[ImageUrlBuilder] Found _ref:', assetId);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!assetId) {
|
|
159
|
-
console.warn('[ImageUrlBuilder] Could not extract asset ID from source:', this._source);
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const finalUrl = `/media/${assetId}/image`;
|
|
164
|
-
console.log('[ImageUrlBuilder] Building CDN URL:', finalUrl);
|
|
165
|
-
return finalUrl;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Alias for url()
|
|
170
|
-
*/
|
|
171
|
-
toString(): string | null {
|
|
172
|
-
return this.url();
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Factory function to create an image URL builder
|
|
178
|
-
*
|
|
179
|
-
* Note: Currently returns direct S3/R2 URLs without transformations.
|
|
180
|
-
* The baseUrl parameter is accepted for future compatibility but not currently used
|
|
181
|
-
* since assets already contain their full public URLs.
|
|
182
|
-
*
|
|
183
|
-
* For multi-tenant access with API keys, provide a signAssetUrl function:
|
|
184
|
-
* const urlFor = imageUrlBuilder({
|
|
185
|
-
* signAssetUrl: (assetId) => `/api/assets/${assetId}?token=${generateToken(assetId)}`
|
|
186
|
-
* })
|
|
187
|
-
*
|
|
188
|
-
* Usage:
|
|
189
|
-
* const urlFor = imageUrlBuilder({ baseUrl: 'https://yourdomain.com' })
|
|
190
|
-
* const url = urlFor(image).url() // Returns asset.url directly
|
|
191
|
-
*
|
|
192
|
-
* Future usage with transformations:
|
|
193
|
-
* const url = urlFor(image).width(800).quality(80).url()
|
|
194
|
-
* // Will return transformed image once dynamic rendering is implemented
|
|
195
|
-
*/
|
|
196
|
-
export function imageUrlBuilder() {
|
|
197
|
-
return (source?: ImageValue | ImageAsset | string | Asset | null) => {
|
|
198
|
-
const builder = new ImageUrlBuilder();
|
|
199
|
-
if (source) {
|
|
200
|
-
builder.image(source);
|
|
201
|
-
}
|
|
202
|
-
return builder;
|
|
203
|
-
};
|
|
204
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Aphex CMS Utilities
|
|
2
|
-
|
|
3
|
-
// Schema utilities
|
|
4
|
-
export * from '../schema-utils/index.js';
|
|
5
|
-
|
|
6
|
-
// Validation utilities
|
|
7
|
-
export * from '../field-validation/utils.js';
|
|
8
|
-
|
|
9
|
-
// Other utilities
|
|
10
|
-
export * from './content-hash.js';
|
|
11
|
-
export * from './slug.js';
|
|
12
|
-
export * from './image-url.js';
|
package/src/utils/slug.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate a URL-friendly slug from a string
|
|
3
|
-
* @param text - The text to convert to a slug
|
|
4
|
-
* @returns A URL-safe slug string
|
|
5
|
-
*/
|
|
6
|
-
export function generateSlug(text: string): string {
|
|
7
|
-
if (!text) return '';
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
text
|
|
11
|
-
.toString()
|
|
12
|
-
.toLowerCase()
|
|
13
|
-
.trim()
|
|
14
|
-
// Replace spaces and multiple special characters with single hyphens
|
|
15
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
16
|
-
// Remove leading and trailing hyphens
|
|
17
|
-
.replace(/^-+|-+$/g, '')
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Validate if a string is a valid slug
|
|
23
|
-
* @param slug - The slug to validate
|
|
24
|
-
* @returns Boolean indicating if the slug is valid
|
|
25
|
-
*/
|
|
26
|
-
export function isValidSlug(slug: string): boolean {
|
|
27
|
-
if (!slug) return false;
|
|
28
|
-
|
|
29
|
-
// Valid slug: lowercase letters, numbers, and hyphens only
|
|
30
|
-
// Cannot start or end with hyphen
|
|
31
|
-
const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
32
|
-
return slugPattern.test(slug);
|
|
33
|
-
}
|