@foliokit/cms-core 0.0.0 → 1.0.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/esm2022/index.js +15 -1
- package/esm2022/index.js.map +1 -1
- package/esm2022/lib/firebase/firebase.config.js +8 -0
- package/esm2022/lib/firebase/firebase.config.js.map +1 -0
- package/esm2022/lib/firebase/firebase.providers.js +54 -0
- package/esm2022/lib/firebase/firebase.providers.js.map +1 -0
- package/esm2022/lib/models/author.model.js +1 -0
- package/esm2022/lib/models/author.model.js.map +1 -0
- package/esm2022/lib/models/page.model.js +1 -0
- package/esm2022/lib/models/page.model.js.map +1 -0
- package/esm2022/lib/models/post.model.js +1 -0
- package/esm2022/lib/models/post.model.js.map +1 -0
- package/esm2022/lib/models/site-config.model.js +1 -0
- package/esm2022/lib/models/site-config.model.js.map +1 -0
- package/esm2022/lib/models/tag.model.js +1 -0
- package/esm2022/lib/models/tag.model.js.map +1 -0
- package/esm2022/lib/services/auth.service.js +42 -0
- package/esm2022/lib/services/auth.service.js.map +1 -0
- package/esm2022/lib/services/page.service.js +73 -0
- package/esm2022/lib/services/page.service.js.map +1 -0
- package/esm2022/lib/services/post.service.js +83 -0
- package/esm2022/lib/services/post.service.js.map +1 -0
- package/esm2022/lib/services/site-config.service.js +31 -0
- package/esm2022/lib/services/site-config.service.js.map +1 -0
- package/esm2022/lib/services/tag.service.js +22 -0
- package/esm2022/lib/services/tag.service.js.map +1 -0
- package/esm2022/lib/tokens/page-service.token.js +4 -0
- package/esm2022/lib/tokens/page-service.token.js.map +1 -0
- package/esm2022/lib/tokens/post-service.token.js +5 -0
- package/esm2022/lib/tokens/post-service.token.js.map +1 -0
- package/esm2022/lib/utils/normalize-page.js +74 -0
- package/esm2022/lib/utils/normalize-page.js.map +1 -0
- package/esm2022/lib/utils/normalize-post.js +66 -0
- package/esm2022/lib/utils/normalize-post.js.map +1 -0
- package/esm2022/lib/utils/normalize-site-config.js +62 -0
- package/esm2022/lib/utils/normalize-site-config.js.map +1 -0
- package/index.d.ts +14 -1
- package/lib/firebase/firebase.config.d.ts +11 -0
- package/lib/firebase/firebase.providers.d.ts +3 -0
- package/lib/models/author.model.d.ts +14 -0
- package/lib/models/page.model.d.ts +40 -0
- package/lib/models/post.model.d.ts +39 -0
- package/lib/models/site-config.model.d.ts +27 -0
- package/lib/models/tag.model.d.ts +5 -0
- package/lib/services/auth.service.d.ts +13 -0
- package/lib/services/page.service.d.ts +15 -0
- package/lib/services/post.service.d.ts +17 -0
- package/lib/services/site-config.service.d.ts +10 -0
- package/lib/services/tag.service.d.ts +9 -0
- package/lib/tokens/page-service.token.d.ts +9 -0
- package/lib/tokens/post-service.token.d.ts +10 -0
- package/lib/utils/normalize-page.d.ts +2 -0
- package/lib/utils/normalize-post.d.ts +6 -0
- package/lib/utils/normalize-site-config.d.ts +2 -0
- package/package.json +18 -3
- package/esm2022/lib/cms-core/cms-core.js +0 -11
- package/esm2022/lib/cms-core/cms-core.js.map +0 -1
- package/lib/cms-core/cms-core.d.ts +0 -5
package/esm2022/index.js
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
|
-
export * from './lib/
|
|
1
|
+
export * from './lib/firebase/firebase.config';
|
|
2
|
+
export * from './lib/firebase/firebase.providers';
|
|
3
|
+
// firebase-admin.ts is intentionally excluded — server-only, import directly in SSR server files
|
|
4
|
+
export * from './lib/models/post.model';
|
|
5
|
+
export * from './lib/models/site-config.model';
|
|
6
|
+
export * from './lib/models/page.model';
|
|
7
|
+
export * from './lib/models/tag.model';
|
|
8
|
+
export * from './lib/models/author.model';
|
|
9
|
+
export * from './lib/services/auth.service';
|
|
10
|
+
export * from './lib/services/post.service';
|
|
11
|
+
export * from './lib/services/page.service';
|
|
12
|
+
export * from './lib/services/site-config.service';
|
|
13
|
+
export * from './lib/services/tag.service';
|
|
14
|
+
export * from './lib/tokens/post-service.token';
|
|
15
|
+
export * from './lib/tokens/page-service.token';
|
|
2
16
|
//# sourceMappingURL=index.js.map
|
package/esm2022/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/cms-core/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC","sourcesContent":["export * from './lib/
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/cms-core/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,iGAAiG;AACjG,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC","sourcesContent":["export * from './lib/firebase/firebase.config';\nexport * from './lib/firebase/firebase.providers';\n// firebase-admin.ts is intentionally excluded — server-only, import directly in SSR server files\nexport * from './lib/models/post.model';\nexport * from './lib/models/site-config.model';\nexport * from './lib/models/page.model';\nexport * from './lib/models/tag.model';\nexport * from './lib/models/author.model';\nexport * from './lib/services/auth.service';\nexport * from './lib/services/post.service';\nexport * from './lib/services/page.service';\nexport * from './lib/services/site-config.service';\nexport * from './lib/services/tag.service';\nexport * from './lib/tokens/post-service.token';\nexport * from './lib/tokens/page-service.token';\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
export const FIREBASE_OPTIONS = new InjectionToken('FIREBASE_OPTIONS');
|
|
3
|
+
export const FIREBASE_APP = new InjectionToken('FIREBASE_APP');
|
|
4
|
+
export const FIRESTORE = new InjectionToken('FIRESTORE');
|
|
5
|
+
export const FIREBASE_STORAGE = new InjectionToken('FIREBASE_STORAGE');
|
|
6
|
+
export const FIREBASE_AUTH = new InjectionToken('FIREBASE_AUTH');
|
|
7
|
+
export const ADMIN_EMAIL = new InjectionToken('ADMIN_EMAIL');
|
|
8
|
+
//# sourceMappingURL=firebase.config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firebase.config.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/firebase/firebase.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAM/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,cAAc,CAAkB,kBAAkB,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,cAAc,CAAc,cAAc,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,cAAc,CAAY,WAAW,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,cAAc,CAAkB,kBAAkB,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,cAAc,CAAc,eAAe,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,cAAc,CAAS,aAAa,CAAC,CAAC","sourcesContent":["import { InjectionToken } from '@angular/core';\nimport type { FirebaseApp, FirebaseOptions } from 'firebase/app';\nimport type { Firestore } from 'firebase/firestore';\nimport type { FirebaseStorage } from 'firebase/storage';\nimport type { Auth } from 'firebase/auth';\n\nexport const FIREBASE_OPTIONS = new InjectionToken<FirebaseOptions>('FIREBASE_OPTIONS');\nexport const FIREBASE_APP = new InjectionToken<FirebaseApp>('FIREBASE_APP');\nexport const FIRESTORE = new InjectionToken<Firestore>('FIRESTORE');\nexport const FIREBASE_STORAGE = new InjectionToken<FirebaseStorage>('FIREBASE_STORAGE');\nexport const FIREBASE_AUTH = new InjectionToken<Auth | null>('FIREBASE_AUTH');\nexport const ADMIN_EMAIL = new InjectionToken<string>('ADMIN_EMAIL');\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PLATFORM_ID, inject, makeEnvironmentProviders, } from '@angular/core';
|
|
2
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
3
|
+
import { getApp, getApps, initializeApp } from 'firebase/app';
|
|
4
|
+
import { connectFirestoreEmulator, getFirestore, initializeFirestore, memoryLocalCache, } from 'firebase/firestore';
|
|
5
|
+
import { getStorage } from 'firebase/storage';
|
|
6
|
+
import { getAuth } from 'firebase/auth';
|
|
7
|
+
import { FIREBASE_APP, FIREBASE_AUTH, FIREBASE_OPTIONS, FIREBASE_STORAGE, FIRESTORE, } from './firebase.config';
|
|
8
|
+
export function provideFirebase(options, useEmulator = false) {
|
|
9
|
+
return makeEnvironmentProviders([
|
|
10
|
+
{
|
|
11
|
+
provide: FIREBASE_OPTIONS,
|
|
12
|
+
useValue: options,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
// Firebase app is a Node.js-process-level singleton. In SSR, each request
|
|
16
|
+
// creates a new DI context but shares the same global Firebase registry, so
|
|
17
|
+
// we must reuse the existing app rather than calling initializeApp again.
|
|
18
|
+
provide: FIREBASE_APP,
|
|
19
|
+
useFactory: () => getApps().length ? getApp() : initializeApp(inject(FIREBASE_OPTIONS)),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
// Same singleton constraint applies to the Firestore instance.
|
|
23
|
+
// initializeFirestore throws if called a second time for the same app, so
|
|
24
|
+
// fall back to getFirestore() which returns the already-configured instance.
|
|
25
|
+
provide: FIRESTORE,
|
|
26
|
+
useFactory: () => {
|
|
27
|
+
const app = inject(FIREBASE_APP);
|
|
28
|
+
try {
|
|
29
|
+
const db = initializeFirestore(app, { localCache: memoryLocalCache() });
|
|
30
|
+
if (useEmulator) {
|
|
31
|
+
connectFirestoreEmulator(db, '127.0.0.1', 8080);
|
|
32
|
+
}
|
|
33
|
+
return db;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return getFirestore(app);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
provide: FIREBASE_STORAGE,
|
|
42
|
+
useFactory: () => getStorage(inject(FIREBASE_APP)),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
// Auth relies on browser APIs (IndexedDB, localStorage) — null on server
|
|
46
|
+
provide: FIREBASE_AUTH,
|
|
47
|
+
useFactory: () => {
|
|
48
|
+
const platformId = inject(PLATFORM_ID);
|
|
49
|
+
return isPlatformBrowser(platformId) ? getAuth(inject(FIREBASE_APP)) : null;
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=firebase.providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firebase.providers.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/firebase/firebase.providers.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EACX,MAAM,EACN,wBAAwB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAmB,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EACL,wBAAwB,EACxB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,MAAM,UAAU,eAAe,CAC7B,OAAwB,EACxB,WAAW,GAAG,KAAK;IAEnB,OAAO,wBAAwB,CAAC;QAC9B;YACE,OAAO,EAAE,gBAAgB;YACzB,QAAQ,EAAE,OAAO;SAClB;QACD;YACE,0EAA0E;YAC1E,4EAA4E;YAC5E,0EAA0E;YAC1E,OAAO,EAAE,YAAY;YACrB,UAAU,EAAE,GAAG,EAAE,CACf,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;SACxE;QACD;YACE,+DAA+D;YAC/D,0EAA0E;YAC1E,6EAA6E;YAC7E,OAAO,EAAE,SAAS;YAClB,UAAU,EAAE,GAAG,EAAE;gBACf,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;gBACjC,IAAI,CAAC;oBACH,MAAM,EAAE,GAAG,mBAAmB,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC;oBACxE,IAAI,WAAW,EAAE,CAAC;wBAChB,wBAAwB,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;oBAClD,CAAC;oBACD,OAAO,EAAE,CAAC;gBACZ,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;SACF;QACD;YACE,OAAO,EAAE,gBAAgB;YACzB,UAAU,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;SACnD;QACD;YACE,yEAAyE;YACzE,OAAO,EAAE,aAAa;YACtB,UAAU,EAAE,GAAG,EAAE;gBACf,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;gBACvC,OAAO,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC9E,CAAC;SACF;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import {\n EnvironmentProviders,\n PLATFORM_ID,\n inject,\n makeEnvironmentProviders,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { FirebaseOptions, getApp, getApps, initializeApp } from 'firebase/app';\nimport {\n connectFirestoreEmulator,\n getFirestore,\n initializeFirestore,\n memoryLocalCache,\n} from 'firebase/firestore';\nimport { getStorage } from 'firebase/storage';\nimport { getAuth } from 'firebase/auth';\nimport {\n FIREBASE_APP,\n FIREBASE_AUTH,\n FIREBASE_OPTIONS,\n FIREBASE_STORAGE,\n FIRESTORE,\n} from './firebase.config';\n\nexport function provideFirebase(\n options: FirebaseOptions,\n useEmulator = false\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: FIREBASE_OPTIONS,\n useValue: options,\n },\n {\n // Firebase app is a Node.js-process-level singleton. In SSR, each request\n // creates a new DI context but shares the same global Firebase registry, so\n // we must reuse the existing app rather than calling initializeApp again.\n provide: FIREBASE_APP,\n useFactory: () =>\n getApps().length ? getApp() : initializeApp(inject(FIREBASE_OPTIONS)),\n },\n {\n // Same singleton constraint applies to the Firestore instance.\n // initializeFirestore throws if called a second time for the same app, so\n // fall back to getFirestore() which returns the already-configured instance.\n provide: FIRESTORE,\n useFactory: () => {\n const app = inject(FIREBASE_APP);\n try {\n const db = initializeFirestore(app, { localCache: memoryLocalCache() });\n if (useEmulator) {\n connectFirestoreEmulator(db, '127.0.0.1', 8080);\n }\n return db;\n } catch {\n return getFirestore(app);\n }\n },\n },\n {\n provide: FIREBASE_STORAGE,\n useFactory: () => getStorage(inject(FIREBASE_APP)),\n },\n {\n // Auth relies on browser APIs (IndexedDB, localStorage) — null on server\n provide: FIREBASE_AUTH,\n useFactory: () => {\n const platformId = inject(PLATFORM_ID);\n return isPlatformBrowser(platformId) ? getAuth(inject(FIREBASE_APP)) : null;\n },\n },\n ]);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=author.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"author.model.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/models/author.model.ts"],"names":[],"mappings":"","sourcesContent":["import type { SocialLink } from './site-config.model';\n\nexport interface Author {\n id: string;\n name: string;\n slug: string;\n bio?: string;\n avatarUrl?: string;\n email?: string;\n social?: SocialLink[];\n /** Unix milliseconds. */\n createdAt: number;\n /** Unix milliseconds. */\n updatedAt: number;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=page.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.model.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/models/page.model.ts"],"names":[],"mappings":"","sourcesContent":["import type { SeoMeta, EmbeddedMediaEntry } from './post.model';\nimport type { SocialPlatform } from './site-config.model';\n\nexport interface CmsPageBase {\n id: string;\n type: 'about' | 'links';\n slug: string;\n title: string;\n status: 'draft' | 'published';\n seo: SeoMeta;\n /** Unix milliseconds — same convention as BlogPost. */\n updatedAt: number;\n /** Unix milliseconds. */\n createdAt: number;\n}\n\nexport interface AboutPage extends CmsPageBase {\n type: 'about';\n heroImageUrl?: string;\n heroImageAlt?: string;\n body: string;\n contentVersion: number;\n embeddedMedia: Record<string, EmbeddedMediaEntry>;\n}\n\nexport interface LinksLink {\n id: string;\n label: string;\n url: string;\n icon?: string;\n platform?: SocialPlatform;\n highlighted?: boolean;\n order: number;\n}\n\nexport interface LinksPage extends CmsPageBase {\n type: 'links';\n avatarUrl?: string;\n avatarAlt?: string;\n headline?: string;\n bio?: string;\n links: LinksLink[];\n}\n\nexport type CmsPageUnion = AboutPage | LinksPage;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=post.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post.model.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/models/post.model.ts"],"names":[],"mappings":"","sourcesContent":["export interface SeoMeta {\n title?: string;\n description?: string;\n keywords?: string[];\n ogImage?: string;\n canonicalUrl?: string;\n noIndex?: boolean;\n}\n\nexport interface EmbeddedMediaEntry {\n token: string;\n storagePath: string;\n downloadUrl: string;\n alt: string;\n mimeType: string;\n}\n\nexport interface BlogPost {\n id: string;\n slug: string;\n title: string;\n subtitle?: string;\n status: 'published' | 'draft' | 'scheduled' | 'archived';\n content: string;\n excerpt?: string;\n thumbnailUrl?: string;\n thumbnailAlt?: string;\n tags: string[];\n authorId?: string;\n readingTimeMinutes?: number;\n embeddedMedia: Record<string, EmbeddedMediaEntry>;\n seo: SeoMeta;\n /** Unix milliseconds. Stored as Firestore Timestamp but always normalized on read. */\n publishedAt: number;\n /** Unix milliseconds, optional. */\n scheduledPublishAt?: number;\n /** Unix milliseconds. */\n updatedAt: number;\n /** Unix milliseconds. */\n createdAt: number;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=site-config.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-config.model.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/models/site-config.model.ts"],"names":[],"mappings":"","sourcesContent":["export interface NavItem {\n label: string;\n url: string;\n order?: number;\n external?: boolean;\n icon?: string;\n}\n\nexport type SocialPlatform =\n | 'twitter'\n | 'instagram'\n | 'github'\n | 'linkedin'\n | 'youtube'\n | 'twitch'\n | 'bluesky'\n | 'tiktok'\n | 'facebook'\n | 'email'\n | 'website';\n\nexport interface SocialLink {\n platform: SocialPlatform;\n url: string;\n label?: string;\n icon?: string;\n}\n\nexport interface SiteConfig {\n id: string;\n siteName: string;\n siteUrl: string;\n description?: string;\n logo?: string;\n favicon?: string;\n nav: NavItem[];\n social: SocialLink[];\n defaultAuthorId?: string;\n /** Unix milliseconds. */\n updatedAt: number;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=tag.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag.model.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/models/tag.model.ts"],"names":[],"mappings":"","sourcesContent":["export interface Tag {\n id: string;\n label: string;\n slug: string;\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { computed, inject, Injectable } from '@angular/core';
|
|
2
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
import { GoogleAuthProvider, onAuthStateChanged, signInWithPopup, signOut, } from 'firebase/auth';
|
|
5
|
+
import { ADMIN_EMAIL, FIREBASE_AUTH } from '../firebase/firebase.config';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
export class AuthService {
|
|
8
|
+
auth = inject(FIREBASE_AUTH);
|
|
9
|
+
adminEmail = inject(ADMIN_EMAIL, { optional: true });
|
|
10
|
+
user = toSignal(new Observable((subscriber) => {
|
|
11
|
+
if (!this.auth) {
|
|
12
|
+
subscriber.next(null);
|
|
13
|
+
subscriber.complete();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
return onAuthStateChanged(this.auth, (u) => subscriber.next(u));
|
|
17
|
+
}), { requireSync: false, initialValue: undefined });
|
|
18
|
+
isAuthenticated = computed(() => this.user() != null, ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
19
|
+
isAdmin = computed(() => {
|
|
20
|
+
const email = this.user()?.email;
|
|
21
|
+
if (!email)
|
|
22
|
+
return false;
|
|
23
|
+
return this.adminEmail ? email === this.adminEmail : false;
|
|
24
|
+
}, ...(ngDevMode ? [{ debugName: "isAdmin" }] : /* istanbul ignore next */ []));
|
|
25
|
+
async signInWithGoogle() {
|
|
26
|
+
if (!this.auth)
|
|
27
|
+
return;
|
|
28
|
+
await signInWithPopup(this.auth, new GoogleAuthProvider());
|
|
29
|
+
}
|
|
30
|
+
async signOut() {
|
|
31
|
+
if (!this.auth)
|
|
32
|
+
return;
|
|
33
|
+
await signOut(this.auth);
|
|
34
|
+
}
|
|
35
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: AuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
36
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: AuthService, providedIn: 'root' });
|
|
37
|
+
}
|
|
38
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: AuthService, decorators: [{
|
|
39
|
+
type: Injectable,
|
|
40
|
+
args: [{ providedIn: 'root' }]
|
|
41
|
+
}] });
|
|
42
|
+
//# sourceMappingURL=auth.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/services/auth.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,OAAO,GAER,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;;AAGzE,MAAM,OAAO,WAAW;IACL,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IAC7B,UAAU,GAAG,MAAM,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7D,IAAI,GAAG,QAAQ,CACtB,IAAI,UAAU,CAAc,CAAC,UAAU,EAAE,EAAE;QACzC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtB,UAAU,CAAC,QAAQ,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QACD,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC,CAAC,EACF,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,CAChD,CAAC;IAEO,eAAe,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,sFAAC,CAAC;IAEtD,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;IAC7D,CAAC,8EAAC,CAAC;IAEH,KAAK,CAAC,gBAAgB;QACpB,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO;QACvB,MAAM,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,kBAAkB,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO;QACvB,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;uGAhCU,WAAW;2GAAX,WAAW,cADE,MAAM;;2FACnB,WAAW;kBADvB,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { computed, inject, Injectable } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { Observable } from 'rxjs';\nimport {\n GoogleAuthProvider,\n onAuthStateChanged,\n signInWithPopup,\n signOut,\n User,\n} from 'firebase/auth';\nimport { ADMIN_EMAIL, FIREBASE_AUTH } from '../firebase/firebase.config';\n\n@Injectable({ providedIn: 'root' })\nexport class AuthService {\n private readonly auth = inject(FIREBASE_AUTH);\n private readonly adminEmail = inject(ADMIN_EMAIL, { optional: true });\n\n readonly user = toSignal(\n new Observable<User | null>((subscriber) => {\n if (!this.auth) {\n subscriber.next(null);\n subscriber.complete();\n return;\n }\n return onAuthStateChanged(this.auth, (u) => subscriber.next(u));\n }),\n { requireSync: false, initialValue: undefined },\n );\n\n readonly isAuthenticated = computed(() => this.user() != null);\n\n readonly isAdmin = computed(() => {\n const email = this.user()?.email;\n if (!email) return false;\n return this.adminEmail ? email === this.adminEmail : false;\n });\n\n async signInWithGoogle(): Promise<void> {\n if (!this.auth) return;\n await signInWithPopup(this.auth, new GoogleAuthProvider());\n }\n\n async signOut(): Promise<void> {\n if (!this.auth) return;\n await signOut(this.auth);\n }\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { inject, Injectable } from '@angular/core';
|
|
2
|
+
import { collection, doc, getDoc, getDocs, limit, orderBy, query, setDoc, Timestamp, updateDoc, where, } from 'firebase/firestore';
|
|
3
|
+
import { deleteObject, ref } from 'firebase/storage';
|
|
4
|
+
import { from, of } from 'rxjs';
|
|
5
|
+
import { catchError, map } from 'rxjs/operators';
|
|
6
|
+
import { FIREBASE_STORAGE, FIRESTORE } from '../firebase/firebase.config';
|
|
7
|
+
import { normalizePage } from '../utils/normalize-page';
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
function omitUndefined(obj) {
|
|
10
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
11
|
+
}
|
|
12
|
+
export class PageService {
|
|
13
|
+
firestore = inject(FIRESTORE);
|
|
14
|
+
storage = inject(FIREBASE_STORAGE);
|
|
15
|
+
getPageBySlug(slug) {
|
|
16
|
+
const q = query(collection(this.firestore, 'pages'), where('status', '==', 'published'), where('slug', '==', slug), limit(1));
|
|
17
|
+
return from(getDocs(q)).pipe(map((snapshot) => {
|
|
18
|
+
if (snapshot.empty)
|
|
19
|
+
return null;
|
|
20
|
+
const d = snapshot.docs[0];
|
|
21
|
+
return normalizePage({ id: d.id, ...d.data() });
|
|
22
|
+
}), catchError((err) => {
|
|
23
|
+
console.error('[PageService.getPageBySlug]', err);
|
|
24
|
+
return of(null);
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
getAllPages() {
|
|
28
|
+
const q = query(collection(this.firestore, 'pages'), orderBy('updatedAt', 'desc'));
|
|
29
|
+
return from(getDocs(q)).pipe(map((snapshot) => snapshot.docs.map((d) => normalizePage({ id: d.id, ...d.data() }))), catchError((err) => {
|
|
30
|
+
console.error('[PageService.getAllPages]', err);
|
|
31
|
+
return of([]);
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
getPageById(id) {
|
|
35
|
+
return from(getDoc(doc(this.firestore, 'pages', id))).pipe(map((snapshot) => {
|
|
36
|
+
if (!snapshot.exists())
|
|
37
|
+
throw new Error(`Page not found: ${id}`);
|
|
38
|
+
return normalizePage({ id: snapshot.id, ...snapshot.data() });
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
deleteStorageFile(storagePath) {
|
|
42
|
+
const fileRef = ref(this.storage, storagePath);
|
|
43
|
+
return from(deleteObject(fileRef));
|
|
44
|
+
}
|
|
45
|
+
savePage(page) {
|
|
46
|
+
const nowMs = Date.now();
|
|
47
|
+
const nowTs = Timestamp.fromMillis(nowMs);
|
|
48
|
+
if (page.id === '') {
|
|
49
|
+
const newId = page.type === 'about' || page.type === 'links'
|
|
50
|
+
? page.type
|
|
51
|
+
: doc(collection(this.firestore, 'pages')).id;
|
|
52
|
+
const savedPage = { ...page, id: newId, createdAt: nowMs, updatedAt: nowMs };
|
|
53
|
+
const firestorePayload = omitUndefined({ ...savedPage, createdAt: nowTs, updatedAt: nowTs });
|
|
54
|
+
return from(setDoc(doc(this.firestore, 'pages', newId), firestorePayload)).pipe(map(() => savedPage), catchError((err) => {
|
|
55
|
+
console.error('[PageService.savePage/create]', err);
|
|
56
|
+
throw err;
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
const savedPage = { ...page, updatedAt: nowMs };
|
|
60
|
+
const firestorePayload = omitUndefined({ ...savedPage, updatedAt: nowTs });
|
|
61
|
+
return from(updateDoc(doc(this.firestore, 'pages', page.id), firestorePayload)).pipe(map(() => savedPage), catchError((err) => {
|
|
62
|
+
console.error('[PageService.savePage/update]', err);
|
|
63
|
+
throw err;
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
67
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PageService, providedIn: 'root' });
|
|
68
|
+
}
|
|
69
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PageService, decorators: [{
|
|
70
|
+
type: Injectable,
|
|
71
|
+
args: [{ providedIn: 'root' }]
|
|
72
|
+
}] });
|
|
73
|
+
//# sourceMappingURL=page.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.service.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/services/page.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport {\n collection,\n doc,\n getDoc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n Timestamp,\n updateDoc,\n where,\n} from 'firebase/firestore';\nimport { deleteObject, ref } from 'firebase/storage';\nimport { from, Observable, of } from 'rxjs';\nimport { catchError, map } from 'rxjs/operators';\nimport { FIREBASE_STORAGE, FIRESTORE } from '../firebase/firebase.config';\nimport type { CmsPageUnion } from '../models/page.model';\nimport { normalizePage } from '../utils/normalize-page';\nimport type { IPageService } from '../tokens/page-service.token';\n\nfunction omitUndefined(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));\n}\n\n@Injectable({ providedIn: 'root' })\nexport class PageService implements IPageService {\n private readonly firestore = inject(FIRESTORE);\n private readonly storage = inject(FIREBASE_STORAGE);\n\n getPageBySlug(slug: string): Observable<CmsPageUnion | null> {\n const q = query(\n collection(this.firestore, 'pages'),\n where('status', '==', 'published'),\n where('slug', '==', slug),\n limit(1),\n );\n return from(getDocs(q)).pipe(\n map((snapshot) => {\n if (snapshot.empty) return null;\n const d = snapshot.docs[0];\n return normalizePage({ id: d.id, ...d.data() });\n }),\n catchError((err) => {\n console.error('[PageService.getPageBySlug]', err);\n return of(null);\n }),\n );\n }\n\n getAllPages(): Observable<CmsPageUnion[]> {\n const q = query(\n collection(this.firestore, 'pages'),\n orderBy('updatedAt', 'desc'),\n );\n return from(getDocs(q)).pipe(\n map((snapshot) =>\n snapshot.docs.map((d) => normalizePage({ id: d.id, ...d.data() })),\n ),\n catchError((err) => {\n console.error('[PageService.getAllPages]', err);\n return of([]);\n }),\n );\n }\n\n getPageById(id: string): Observable<CmsPageUnion> {\n return from(getDoc(doc(this.firestore, 'pages', id))).pipe(\n map((snapshot) => {\n if (!snapshot.exists()) throw new Error(`Page not found: ${id}`);\n return normalizePage({ id: snapshot.id, ...snapshot.data() });\n }),\n );\n }\n\n deleteStorageFile(storagePath: string): Observable<void> {\n const fileRef = ref(this.storage, storagePath);\n return from(deleteObject(fileRef));\n }\n\n savePage(page: CmsPageUnion): Observable<CmsPageUnion> {\n const nowMs = Date.now();\n const nowTs = Timestamp.fromMillis(nowMs);\n\n if (page.id === '') {\n const newId =\n page.type === 'about' || page.type === 'links'\n ? page.type\n : doc(collection(this.firestore, 'pages')).id;\n const savedPage: CmsPageUnion = { ...page, id: newId, createdAt: nowMs, updatedAt: nowMs };\n const firestorePayload = omitUndefined({ ...savedPage, createdAt: nowTs, updatedAt: nowTs });\n return from(\n setDoc(doc(this.firestore, 'pages', newId), firestorePayload),\n ).pipe(\n map(() => savedPage),\n catchError((err) => {\n console.error('[PageService.savePage/create]', err);\n throw err;\n }),\n );\n }\n\n const savedPage: CmsPageUnion = { ...page, updatedAt: nowMs };\n const firestorePayload = omitUndefined({ ...savedPage, updatedAt: nowTs });\n return from(\n updateDoc(doc(this.firestore, 'pages', page.id), firestorePayload),\n ).pipe(\n map(() => savedPage),\n catchError((err) => {\n console.error('[PageService.savePage/update]', err);\n throw err;\n }),\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { inject, Injectable } from '@angular/core';
|
|
2
|
+
import { collection, doc, getDoc, getDocs, limit, orderBy, query, setDoc, Timestamp, updateDoc, where, } from 'firebase/firestore';
|
|
3
|
+
import { deleteObject, ref } from 'firebase/storage';
|
|
4
|
+
import { from, of } from 'rxjs';
|
|
5
|
+
import { catchError, map } from 'rxjs/operators';
|
|
6
|
+
import { FIREBASE_STORAGE, FIRESTORE } from '../firebase/firebase.config';
|
|
7
|
+
import { normalizePost } from '../utils/normalize-post';
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
export class PostService {
|
|
10
|
+
firestore = inject(FIRESTORE);
|
|
11
|
+
storage = inject(FIREBASE_STORAGE);
|
|
12
|
+
getPublishedPosts() {
|
|
13
|
+
const q = query(collection(this.firestore, 'posts'), where('status', '==', 'published'), orderBy('publishedAt', 'desc'));
|
|
14
|
+
return from(getDocs(q)).pipe(map((snapshot) => snapshot.docs.map((d) => normalizePost({ id: d.id, ...d.data() }))), catchError((err) => {
|
|
15
|
+
console.error('[PostService.getPublishedPosts]', err);
|
|
16
|
+
return of([]);
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
getPostBySlug(slug) {
|
|
20
|
+
const q = query(collection(this.firestore, 'posts'), where('status', '==', 'published'), where('slug', '==', slug), limit(1));
|
|
21
|
+
return from(getDocs(q)).pipe(map((snapshot) => {
|
|
22
|
+
if (snapshot.empty)
|
|
23
|
+
return null;
|
|
24
|
+
const d = snapshot.docs[0];
|
|
25
|
+
return normalizePost({ id: d.id, ...d.data() });
|
|
26
|
+
}), catchError((err) => {
|
|
27
|
+
console.error('[PostService.getPostBySlug]', err);
|
|
28
|
+
return of(null);
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
getPostsByTag(tag) {
|
|
32
|
+
const q = query(collection(this.firestore, 'posts'), where('status', '==', 'published'), where('tags', 'array-contains', tag), orderBy('publishedAt', 'desc'));
|
|
33
|
+
return from(getDocs(q)).pipe(map((snapshot) => snapshot.docs.map((d) => normalizePost({ id: d.id, ...d.data() }))), catchError((err) => {
|
|
34
|
+
console.error('[PostService.getPostsByTag]', err);
|
|
35
|
+
return of([]);
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
getAllPosts() {
|
|
39
|
+
const q = query(collection(this.firestore, 'posts'), orderBy('updatedAt', 'desc'));
|
|
40
|
+
return from(getDocs(q)).pipe(map((snapshot) => snapshot.docs.map((d) => normalizePost({ id: d.id, ...d.data() }))), catchError((err) => {
|
|
41
|
+
console.error('[PostService.getAllPosts]', err);
|
|
42
|
+
return of([]);
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
getPostById(id) {
|
|
46
|
+
return from(getDoc(doc(this.firestore, 'posts', id))).pipe(map((snapshot) => {
|
|
47
|
+
if (!snapshot.exists())
|
|
48
|
+
throw new Error(`Post not found: ${id}`);
|
|
49
|
+
return normalizePost({ id: snapshot.id, ...snapshot.data() });
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
deleteStorageFile(storagePath) {
|
|
53
|
+
const fileRef = ref(this.storage, storagePath);
|
|
54
|
+
return from(deleteObject(fileRef));
|
|
55
|
+
}
|
|
56
|
+
savePost(post) {
|
|
57
|
+
const nowMs = Date.now();
|
|
58
|
+
const nowTs = Timestamp.fromMillis(nowMs);
|
|
59
|
+
if (post.id === '') {
|
|
60
|
+
const newId = doc(collection(this.firestore, 'posts')).id;
|
|
61
|
+
const savedPost = { ...post, id: newId, createdAt: nowMs, updatedAt: nowMs };
|
|
62
|
+
// Write Timestamp objects to Firestore for proper ordering/querying
|
|
63
|
+
const firestorePayload = { ...savedPost, createdAt: nowTs, updatedAt: nowTs };
|
|
64
|
+
return from(setDoc(doc(this.firestore, 'posts', newId), firestorePayload)).pipe(map(() => savedPost), catchError((err) => {
|
|
65
|
+
console.error('[PostService.savePost/create]', err);
|
|
66
|
+
throw err;
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
const savedPost = { ...post, updatedAt: nowMs };
|
|
70
|
+
const firestorePayload = { ...savedPost, updatedAt: nowTs };
|
|
71
|
+
return from(updateDoc(doc(this.firestore, 'posts', post.id), firestorePayload)).pipe(map(() => savedPost), catchError((err) => {
|
|
72
|
+
console.error('[PostService.savePost/update]', err);
|
|
73
|
+
throw err;
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PostService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
77
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PostService, providedIn: 'root' });
|
|
78
|
+
}
|
|
79
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PostService, decorators: [{
|
|
80
|
+
type: Injectable,
|
|
81
|
+
args: [{ providedIn: 'root' }]
|
|
82
|
+
}] });
|
|
83
|
+
//# sourceMappingURL=post.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post.service.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/services/post.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport {\n collection,\n doc,\n getDoc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n Timestamp,\n updateDoc,\n where,\n} from 'firebase/firestore';\nimport { deleteObject, ref } from 'firebase/storage';\nimport { from, Observable, of } from 'rxjs';\nimport { catchError, map } from 'rxjs/operators';\nimport { FIREBASE_STORAGE, FIRESTORE } from '../firebase/firebase.config';\nimport type { BlogPost } from '../models/post.model';\nimport { normalizePost } from '../utils/normalize-post';\nimport type { IBlogPostService } from '../tokens/post-service.token';\n\n@Injectable({ providedIn: 'root' })\nexport class PostService implements IBlogPostService {\n private readonly firestore = inject(FIRESTORE);\n private readonly storage = inject(FIREBASE_STORAGE);\n\n getPublishedPosts(): Observable<BlogPost[]> {\n const q = query(\n collection(this.firestore, 'posts'),\n where('status', '==', 'published'),\n orderBy('publishedAt', 'desc'),\n );\n return from(getDocs(q)).pipe(\n map((snapshot) =>\n snapshot.docs.map((d) => normalizePost({ id: d.id, ...d.data() })),\n ),\n catchError((err) => {\n console.error('[PostService.getPublishedPosts]', err);\n return of([]);\n }),\n );\n }\n\n getPostBySlug(slug: string): Observable<BlogPost | null> {\n const q = query(\n collection(this.firestore, 'posts'),\n where('status', '==', 'published'),\n where('slug', '==', slug),\n limit(1),\n );\n return from(getDocs(q)).pipe(\n map((snapshot) => {\n if (snapshot.empty) return null;\n const d = snapshot.docs[0];\n return normalizePost({ id: d.id, ...d.data() });\n }),\n catchError((err) => {\n console.error('[PostService.getPostBySlug]', err);\n return of(null);\n }),\n );\n }\n\n getPostsByTag(tag: string): Observable<BlogPost[]> {\n const q = query(\n collection(this.firestore, 'posts'),\n where('status', '==', 'published'),\n where('tags', 'array-contains', tag),\n orderBy('publishedAt', 'desc'),\n );\n return from(getDocs(q)).pipe(\n map((snapshot) =>\n snapshot.docs.map((d) => normalizePost({ id: d.id, ...d.data() })),\n ),\n catchError((err) => {\n console.error('[PostService.getPostsByTag]', err);\n return of([]);\n }),\n );\n }\n\n getAllPosts(): Observable<BlogPost[]> {\n const q = query(\n collection(this.firestore, 'posts'),\n orderBy('updatedAt', 'desc'),\n );\n return from(getDocs(q)).pipe(\n map((snapshot) =>\n snapshot.docs.map((d) => normalizePost({ id: d.id, ...d.data() })),\n ),\n catchError((err) => {\n console.error('[PostService.getAllPosts]', err);\n return of([]);\n }),\n );\n }\n\n getPostById(id: string): Observable<BlogPost> {\n return from(getDoc(doc(this.firestore, 'posts', id))).pipe(\n map((snapshot) => {\n if (!snapshot.exists()) throw new Error(`Post not found: ${id}`);\n return normalizePost({ id: snapshot.id, ...snapshot.data() });\n }),\n );\n }\n\n deleteStorageFile(storagePath: string): Observable<void> {\n const fileRef = ref(this.storage, storagePath);\n return from(deleteObject(fileRef));\n }\n\n savePost(post: BlogPost): Observable<BlogPost> {\n const nowMs = Date.now();\n const nowTs = Timestamp.fromMillis(nowMs);\n\n if (post.id === '') {\n const newId = doc(collection(this.firestore, 'posts')).id;\n const savedPost: BlogPost = { ...post, id: newId, createdAt: nowMs, updatedAt: nowMs };\n // Write Timestamp objects to Firestore for proper ordering/querying\n const firestorePayload = { ...savedPost, createdAt: nowTs, updatedAt: nowTs };\n return from(\n setDoc(doc(this.firestore, 'posts', newId), firestorePayload),\n ).pipe(\n map(() => savedPost),\n catchError((err) => {\n console.error('[PostService.savePost/create]', err);\n throw err;\n }),\n );\n }\n\n const savedPost: BlogPost = { ...post, updatedAt: nowMs };\n const firestorePayload = { ...savedPost, updatedAt: nowTs };\n return from(\n updateDoc(doc(this.firestore, 'posts', post.id), firestorePayload),\n ).pipe(\n map(() => savedPost),\n catchError((err) => {\n console.error('[PostService.savePost/update]', err);\n throw err;\n }),\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { inject, Injectable } from '@angular/core';
|
|
2
|
+
import { doc, getDoc } from 'firebase/firestore';
|
|
3
|
+
import { from, of } from 'rxjs';
|
|
4
|
+
import { catchError, map } from 'rxjs/operators';
|
|
5
|
+
import { FIRESTORE } from '../firebase/firebase.config';
|
|
6
|
+
import { normalizeSiteConfig } from '../utils/normalize-site-config';
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
export class SiteConfigService {
|
|
9
|
+
firestore = inject(FIRESTORE);
|
|
10
|
+
getSiteConfig(siteId) {
|
|
11
|
+
const ref = doc(this.firestore, 'site-config', siteId);
|
|
12
|
+
return from(getDoc(ref)).pipe(map((snap) => {
|
|
13
|
+
if (!snap.exists())
|
|
14
|
+
return null;
|
|
15
|
+
return normalizeSiteConfig({ id: snap.id, ...snap.data() });
|
|
16
|
+
}), catchError((err) => {
|
|
17
|
+
console.error('[SiteConfigService.getSiteConfig]', err);
|
|
18
|
+
return of(null);
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
getDefaultSiteConfig() {
|
|
22
|
+
return this.getSiteConfig('default');
|
|
23
|
+
}
|
|
24
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SiteConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
25
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SiteConfigService, providedIn: 'root' });
|
|
26
|
+
}
|
|
27
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SiteConfigService, decorators: [{
|
|
28
|
+
type: Injectable,
|
|
29
|
+
args: [{ providedIn: 'root' }]
|
|
30
|
+
}] });
|
|
31
|
+
//# sourceMappingURL=site-config.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-config.service.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/services/site-config.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAc,EAAE,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAExD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;;AAGrE,MAAM,OAAO,iBAAiB;IACX,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAE/C,aAAa,CAAC,MAAc;QAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAC3B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACX,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;gBAAE,OAAO,IAAI,CAAC;YAChC,OAAO,mBAAmB,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9D,CAAC,CAAC,EACF,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE;YACjB,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,GAAG,CAAC,CAAC;YACxD,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,oBAAoB;QAClB,OAAO,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;uGAnBU,iBAAiB;2GAAjB,iBAAiB,cADJ,MAAM;;2FACnB,iBAAiB;kBAD7B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { doc, getDoc } from 'firebase/firestore';\nimport { from, Observable, of } from 'rxjs';\nimport { catchError, map } from 'rxjs/operators';\nimport { FIRESTORE } from '../firebase/firebase.config';\nimport type { SiteConfig } from '../models/site-config.model';\nimport { normalizeSiteConfig } from '../utils/normalize-site-config';\n\n@Injectable({ providedIn: 'root' })\nexport class SiteConfigService {\n private readonly firestore = inject(FIRESTORE);\n\n getSiteConfig(siteId: string): Observable<SiteConfig | null> {\n const ref = doc(this.firestore, 'site-config', siteId);\n return from(getDoc(ref)).pipe(\n map((snap) => {\n if (!snap.exists()) return null;\n return normalizeSiteConfig({ id: snap.id, ...snap.data() });\n }),\n catchError((err) => {\n console.error('[SiteConfigService.getSiteConfig]', err);\n return of(null);\n }),\n );\n }\n\n getDefaultSiteConfig(): Observable<SiteConfig | null> {\n return this.getSiteConfig('default');\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { inject, Injectable } from '@angular/core';
|
|
2
|
+
import { collection, getDocs } from 'firebase/firestore';
|
|
3
|
+
import { from, of } from 'rxjs';
|
|
4
|
+
import { catchError, map } from 'rxjs/operators';
|
|
5
|
+
import { FIRESTORE } from '../firebase/firebase.config';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
export class TagService {
|
|
8
|
+
firestore = inject(FIRESTORE);
|
|
9
|
+
getAllTags() {
|
|
10
|
+
return from(getDocs(collection(this.firestore, 'tags'))).pipe(map((snapshot) => snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }))), catchError((err) => {
|
|
11
|
+
console.error('[TagService.getAllTags]', err);
|
|
12
|
+
return of([]);
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
16
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TagService, providedIn: 'root' });
|
|
17
|
+
}
|
|
18
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TagService, decorators: [{
|
|
19
|
+
type: Injectable,
|
|
20
|
+
args: [{ providedIn: 'root' }]
|
|
21
|
+
}] });
|
|
22
|
+
//# sourceMappingURL=tag.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag.service.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/services/tag.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAc,EAAE,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;;AAIxD,MAAM,OAAO,UAAU;IACJ,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAE/C,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAC3D,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CACf,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,EAAE,CAAQ,CAAC,CACnE,EACD,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE;YACjB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAChB,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;uGAbU,UAAU;2GAAV,UAAU,cADG,MAAM;;2FACnB,UAAU;kBADtB,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { collection, getDocs } from 'firebase/firestore';\nimport { from, Observable, of } from 'rxjs';\nimport { catchError, map } from 'rxjs/operators';\nimport { FIRESTORE } from '../firebase/firebase.config';\nimport type { Tag } from '../models/tag.model';\n\n@Injectable({ providedIn: 'root' })\nexport class TagService {\n private readonly firestore = inject(FIRESTORE);\n\n getAllTags(): Observable<Tag[]> {\n return from(getDocs(collection(this.firestore, 'tags'))).pipe(\n map((snapshot) =>\n snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }) as Tag),\n ),\n catchError((err) => {\n console.error('[TagService.getAllTags]', err);\n return of([]);\n }),\n );\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-service.token.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/tokens/page-service.token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAS7D,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,cAAc,CACjD,mBAAmB,CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAsB,UAAU,CAAC,CAAC","sourcesContent":["import { InjectionToken, makeStateKey } from '@angular/core';\nimport type { Observable } from 'rxjs';\nimport type { CmsPageUnion } from '../models/page.model';\n\nexport interface IPageService {\n getPageBySlug(slug: string): Observable<CmsPageUnion | null>;\n getPageById(id: string): Observable<CmsPageUnion | null>;\n}\n\nexport const BLOG_PAGE_SERVICE = new InjectionToken<IPageService>(\n 'BLOG_PAGE_SERVICE',\n);\n\nexport const PAGE_TRANSFER_KEY = makeStateKey<CmsPageUnion | null>('cms-page');\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { InjectionToken, makeStateKey } from '@angular/core';
|
|
2
|
+
export const BLOG_POST_SERVICE = new InjectionToken('BLOG_POST_SERVICE');
|
|
3
|
+
export const POST_TRANSFER_KEY = makeStateKey('blog-post');
|
|
4
|
+
export const POSTS_TRANSFER_KEY = makeStateKey('blog-posts');
|
|
5
|
+
//# sourceMappingURL=post-service.token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-service.token.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/tokens/post-service.token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAS7D,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,cAAc,CACjD,mBAAmB,CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAkB,WAAW,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAa,YAAY,CAAC,CAAC","sourcesContent":["import { InjectionToken, makeStateKey } from '@angular/core';\nimport type { Observable } from 'rxjs';\nimport type { BlogPost } from '../models/post.model';\n\nexport interface IBlogPostService {\n getPublishedPosts(): Observable<BlogPost[]>;\n getPostBySlug(slug: string): Observable<BlogPost | null>;\n}\n\nexport const BLOG_POST_SERVICE = new InjectionToken<IBlogPostService>(\n 'BLOG_POST_SERVICE',\n);\n\nexport const POST_TRANSFER_KEY = makeStateKey<BlogPost | null>('blog-post');\nexport const POSTS_TRANSFER_KEY = makeStateKey<BlogPost[]>('blog-posts');\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function normalizeTimestamp(value) {
|
|
2
|
+
if (value == null)
|
|
3
|
+
return 0;
|
|
4
|
+
if (typeof value === 'number')
|
|
5
|
+
return value;
|
|
6
|
+
if (value instanceof Date)
|
|
7
|
+
return value.getTime();
|
|
8
|
+
if (typeof value === 'object') {
|
|
9
|
+
const v = value;
|
|
10
|
+
if (typeof v.toMillis === 'function') {
|
|
11
|
+
return v.toMillis();
|
|
12
|
+
}
|
|
13
|
+
if (typeof v.toDate === 'function') {
|
|
14
|
+
return v.toDate().getTime();
|
|
15
|
+
}
|
|
16
|
+
if (typeof v['_seconds'] === 'number') {
|
|
17
|
+
return v['_seconds'] * 1000 +
|
|
18
|
+
Math.floor((v['_nanoseconds'] ?? 0) / 1e6);
|
|
19
|
+
}
|
|
20
|
+
if (typeof v['seconds'] === 'number') {
|
|
21
|
+
return v['seconds'] * 1000 +
|
|
22
|
+
Math.floor((v['nanoseconds'] ?? 0) / 1e6);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
function normalizeLinks(raw) {
|
|
28
|
+
if (!Array.isArray(raw))
|
|
29
|
+
return [];
|
|
30
|
+
return raw.map((l, i) => ({
|
|
31
|
+
id: l['id'] ?? crypto.randomUUID(),
|
|
32
|
+
label: l['label'] ?? '',
|
|
33
|
+
url: l['url'] ?? '',
|
|
34
|
+
icon: l['icon'],
|
|
35
|
+
platform: l['platform'],
|
|
36
|
+
highlighted: l['highlighted'],
|
|
37
|
+
order: typeof l['order'] === 'number' ? l['order'] : i,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
export function normalizePage(raw) {
|
|
41
|
+
const type = raw['type'];
|
|
42
|
+
const base = {
|
|
43
|
+
id: raw['id'] ?? '',
|
|
44
|
+
slug: raw['slug'] ?? '',
|
|
45
|
+
title: raw['title'] ?? '',
|
|
46
|
+
status: raw['status'] ?? 'draft',
|
|
47
|
+
seo: raw['seo'] ?? {},
|
|
48
|
+
updatedAt: normalizeTimestamp(raw['updatedAt']),
|
|
49
|
+
createdAt: normalizeTimestamp(raw['createdAt']),
|
|
50
|
+
};
|
|
51
|
+
if (type === 'links') {
|
|
52
|
+
const page = {
|
|
53
|
+
...base,
|
|
54
|
+
type: 'links',
|
|
55
|
+
avatarUrl: raw['avatarUrl'],
|
|
56
|
+
avatarAlt: raw['avatarAlt'],
|
|
57
|
+
headline: raw['headline'],
|
|
58
|
+
bio: raw['bio'],
|
|
59
|
+
links: normalizeLinks(raw['links']),
|
|
60
|
+
};
|
|
61
|
+
return page;
|
|
62
|
+
}
|
|
63
|
+
const page = {
|
|
64
|
+
...base,
|
|
65
|
+
type: 'about',
|
|
66
|
+
heroImageUrl: raw['heroImageUrl'],
|
|
67
|
+
heroImageAlt: raw['heroImageAlt'],
|
|
68
|
+
body: raw['body'] ?? '',
|
|
69
|
+
contentVersion: raw['contentVersion'] ?? 1,
|
|
70
|
+
embeddedMedia: raw['embeddedMedia'] ?? {},
|
|
71
|
+
};
|
|
72
|
+
return page;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=normalize-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-page.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/utils/normalize-page.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AboutPage, CmsPageUnion, LinksPage, LinksLink } from '../models/page.model';\n\nfunction normalizeTimestamp(value: unknown): number {\n if (value == null) return 0;\n if (typeof value === 'number') return value;\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'object') {\n const v = value as Record<string, unknown>;\n if (typeof (v as { toMillis?: unknown }).toMillis === 'function') {\n return (v as { toMillis(): number }).toMillis();\n }\n if (typeof (v as { toDate?: unknown }).toDate === 'function') {\n return (v as { toDate(): Date }).toDate().getTime();\n }\n if (typeof v['_seconds'] === 'number') {\n return (v['_seconds'] as number) * 1000 +\n Math.floor(((v['_nanoseconds'] as number) ?? 0) / 1e6);\n }\n if (typeof v['seconds'] === 'number') {\n return (v['seconds'] as number) * 1000 +\n Math.floor(((v['nanoseconds'] as number) ?? 0) / 1e6);\n }\n }\n return 0;\n}\n\nfunction normalizeLinks(raw: unknown): LinksLink[] {\n if (!Array.isArray(raw)) return [];\n return (raw as Record<string, unknown>[]).map((l, i) => ({\n id: (l['id'] as string) ?? crypto.randomUUID(),\n label: (l['label'] as string) ?? '',\n url: (l['url'] as string) ?? '',\n icon: l['icon'] as string | undefined,\n platform: l['platform'] as LinksLink['platform'],\n highlighted: l['highlighted'] as boolean | undefined,\n order: typeof l['order'] === 'number' ? (l['order'] as number) : i,\n }));\n}\n\nexport function normalizePage(raw: Record<string, unknown>): CmsPageUnion {\n const type = raw['type'] as string;\n const base = {\n id: (raw['id'] as string) ?? '',\n slug: (raw['slug'] as string) ?? '',\n title: (raw['title'] as string) ?? '',\n status: (raw['status'] as CmsPageUnion['status']) ?? 'draft',\n seo: (raw['seo'] as CmsPageUnion['seo']) ?? {},\n updatedAt: normalizeTimestamp(raw['updatedAt']),\n createdAt: normalizeTimestamp(raw['createdAt']),\n };\n\n if (type === 'links') {\n const page: LinksPage = {\n ...base,\n type: 'links',\n avatarUrl: raw['avatarUrl'] as string | undefined,\n avatarAlt: raw['avatarAlt'] as string | undefined,\n headline: raw['headline'] as string | undefined,\n bio: raw['bio'] as string | undefined,\n links: normalizeLinks(raw['links']),\n };\n return page;\n }\n\n const page: AboutPage = {\n ...base,\n type: 'about',\n heroImageUrl: raw['heroImageUrl'] as string | undefined,\n heroImageAlt: raw['heroImageAlt'] as string | undefined,\n body: (raw['body'] as string) ?? '',\n contentVersion: (raw['contentVersion'] as number) ?? 1,\n embeddedMedia: (raw['embeddedMedia'] as AboutPage['embeddedMedia']) ?? {},\n };\n return page;\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a Firestore timestamp in any of its runtime forms to milliseconds.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Native Timestamp objects from either SDK (have .toMillis())
|
|
6
|
+
* - Objects with .toDate() method (client SDK Timestamp without .toMillis())
|
|
7
|
+
* - Admin SDK serialized form { _seconds, _nanoseconds }
|
|
8
|
+
* - Plain { seconds, nanoseconds } objects
|
|
9
|
+
* - Already-numeric millisecond values
|
|
10
|
+
* - JavaScript Date objects
|
|
11
|
+
*/
|
|
12
|
+
function normalizeTimestamp(value) {
|
|
13
|
+
if (value == null)
|
|
14
|
+
return 0;
|
|
15
|
+
if (typeof value === 'number')
|
|
16
|
+
return value;
|
|
17
|
+
if (value instanceof Date)
|
|
18
|
+
return value.getTime();
|
|
19
|
+
if (typeof value === 'object') {
|
|
20
|
+
const v = value;
|
|
21
|
+
if (typeof v.toMillis === 'function') {
|
|
22
|
+
return v.toMillis();
|
|
23
|
+
}
|
|
24
|
+
if (typeof v.toDate === 'function') {
|
|
25
|
+
return v.toDate().getTime();
|
|
26
|
+
}
|
|
27
|
+
if (typeof v['_seconds'] === 'number') {
|
|
28
|
+
return v['_seconds'] * 1000 +
|
|
29
|
+
Math.floor((v['_nanoseconds'] ?? 0) / 1e6);
|
|
30
|
+
}
|
|
31
|
+
if (typeof v['seconds'] === 'number') {
|
|
32
|
+
return v['seconds'] * 1000 +
|
|
33
|
+
Math.floor((v['nanoseconds'] ?? 0) / 1e6);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Maps raw Firestore DocumentData (merged with its doc id) into a fully typed,
|
|
40
|
+
* TransferState-serializable BlogPost. Callers must pass { id: doc.id, ...doc.data() }.
|
|
41
|
+
*/
|
|
42
|
+
export function normalizePost(raw) {
|
|
43
|
+
return {
|
|
44
|
+
id: raw['id'] ?? '',
|
|
45
|
+
slug: raw['slug'] ?? '',
|
|
46
|
+
title: raw['title'] ?? '',
|
|
47
|
+
subtitle: raw['subtitle'],
|
|
48
|
+
status: raw['status'],
|
|
49
|
+
content: raw['content'] ?? '',
|
|
50
|
+
excerpt: raw['excerpt'],
|
|
51
|
+
thumbnailUrl: raw['thumbnailUrl'],
|
|
52
|
+
thumbnailAlt: raw['thumbnailAlt'],
|
|
53
|
+
tags: raw['tags'] ?? [],
|
|
54
|
+
authorId: raw['authorId'],
|
|
55
|
+
readingTimeMinutes: raw['readingTimeMinutes'],
|
|
56
|
+
embeddedMedia: raw['embeddedMedia'] ?? {},
|
|
57
|
+
seo: raw['seo'] ?? {},
|
|
58
|
+
publishedAt: normalizeTimestamp(raw['publishedAt']),
|
|
59
|
+
scheduledPublishAt: raw['scheduledPublishAt'] != null
|
|
60
|
+
? normalizeTimestamp(raw['scheduledPublishAt'])
|
|
61
|
+
: undefined,
|
|
62
|
+
updatedAt: normalizeTimestamp(raw['updatedAt']),
|
|
63
|
+
createdAt: normalizeTimestamp(raw['createdAt']),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=normalize-post.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-post.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/utils/normalize-post.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,CAAC,CAAC;IAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC,OAAO,EAAE,CAAC;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,KAAgC,CAAC;QAC3C,IAAI,OAAQ,CAA4B,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YACjE,OAAQ,CAA4B,CAAC,QAAQ,EAAE,CAAC;QAClD,CAAC;QACD,IAAI,OAAQ,CAA0B,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAC7D,OAAQ,CAAwB,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC;QACtD,CAAC;QACD,IAAI,OAAO,CAAC,CAAC,UAAU,CAAC,KAAK,QAAQ,EAAE,CAAC;YACtC,OAAQ,CAAC,CAAC,UAAU,CAAY,GAAG,IAAI;gBACrC,IAAI,CAAC,KAAK,CAAC,CAAE,CAAC,CAAC,cAAc,CAAY,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,EAAE,CAAC;YACrC,OAAQ,CAAC,CAAC,SAAS,CAAY,GAAG,IAAI;gBACpC,IAAI,CAAC,KAAK,CAAC,CAAE,CAAC,CAAC,aAAa,CAAY,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,GAA4B;IACxD,OAAO;QACL,EAAE,EAAG,GAAG,CAAC,IAAI,CAAY,IAAI,EAAE;QAC/B,IAAI,EAAG,GAAG,CAAC,MAAM,CAAY,IAAI,EAAE;QACnC,KAAK,EAAG,GAAG,CAAC,OAAO,CAAY,IAAI,EAAE;QACrC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAuB;QAC/C,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAuB;QAC3C,OAAO,EAAG,GAAG,CAAC,SAAS,CAAY,IAAI,EAAE;QACzC,OAAO,EAAE,GAAG,CAAC,SAAS,CAAuB;QAC7C,YAAY,EAAE,GAAG,CAAC,cAAc,CAAuB;QACvD,YAAY,EAAE,GAAG,CAAC,cAAc,CAAuB;QACvD,IAAI,EAAG,GAAG,CAAC,MAAM,CAAc,IAAI,EAAE;QACrC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAuB;QAC/C,kBAAkB,EAAE,GAAG,CAAC,oBAAoB,CAAuB;QACnE,aAAa,EAAG,GAAG,CAAC,eAAe,CAA+B,IAAI,EAAE;QACxE,GAAG,EAAG,GAAG,CAAC,KAAK,CAAqB,IAAI,EAAE;QAC1C,WAAW,EAAE,kBAAkB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACnD,kBAAkB,EAChB,GAAG,CAAC,oBAAoB,CAAC,IAAI,IAAI;YAC/B,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;YAC/C,CAAC,CAAC,SAAS;QACf,SAAS,EAAE,kBAAkB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC/C,SAAS,EAAE,kBAAkB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;KAChD,CAAC;AACJ,CAAC","sourcesContent":["import type { BlogPost } from '../models/post.model';\n\n/**\n * Converts a Firestore timestamp in any of its runtime forms to milliseconds.\n *\n * Handles:\n * - Native Timestamp objects from either SDK (have .toMillis())\n * - Objects with .toDate() method (client SDK Timestamp without .toMillis())\n * - Admin SDK serialized form { _seconds, _nanoseconds }\n * - Plain { seconds, nanoseconds } objects\n * - Already-numeric millisecond values\n * - JavaScript Date objects\n */\nfunction normalizeTimestamp(value: unknown): number {\n if (value == null) return 0;\n if (typeof value === 'number') return value;\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'object') {\n const v = value as Record<string, unknown>;\n if (typeof (v as { toMillis?: unknown }).toMillis === 'function') {\n return (v as { toMillis(): number }).toMillis();\n }\n if (typeof (v as { toDate?: unknown }).toDate === 'function') {\n return (v as { toDate(): Date }).toDate().getTime();\n }\n if (typeof v['_seconds'] === 'number') {\n return (v['_seconds'] as number) * 1000 +\n Math.floor(((v['_nanoseconds'] as number) ?? 0) / 1e6);\n }\n if (typeof v['seconds'] === 'number') {\n return (v['seconds'] as number) * 1000 +\n Math.floor(((v['nanoseconds'] as number) ?? 0) / 1e6);\n }\n }\n return 0;\n}\n\n/**\n * Maps raw Firestore DocumentData (merged with its doc id) into a fully typed,\n * TransferState-serializable BlogPost. Callers must pass { id: doc.id, ...doc.data() }.\n */\nexport function normalizePost(raw: Record<string, unknown>): BlogPost {\n return {\n id: (raw['id'] as string) ?? '',\n slug: (raw['slug'] as string) ?? '',\n title: (raw['title'] as string) ?? '',\n subtitle: raw['subtitle'] as string | undefined,\n status: raw['status'] as BlogPost['status'],\n content: (raw['content'] as string) ?? '',\n excerpt: raw['excerpt'] as string | undefined,\n thumbnailUrl: raw['thumbnailUrl'] as string | undefined,\n thumbnailAlt: raw['thumbnailAlt'] as string | undefined,\n tags: (raw['tags'] as string[]) ?? [],\n authorId: raw['authorId'] as string | undefined,\n readingTimeMinutes: raw['readingTimeMinutes'] as number | undefined,\n embeddedMedia: (raw['embeddedMedia'] as BlogPost['embeddedMedia']) ?? {},\n seo: (raw['seo'] as BlogPost['seo']) ?? {},\n publishedAt: normalizeTimestamp(raw['publishedAt']),\n scheduledPublishAt:\n raw['scheduledPublishAt'] != null\n ? normalizeTimestamp(raw['scheduledPublishAt'])\n : undefined,\n updatedAt: normalizeTimestamp(raw['updatedAt']),\n createdAt: normalizeTimestamp(raw['createdAt']),\n };\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function normalizeTimestamp(value) {
|
|
2
|
+
if (value == null)
|
|
3
|
+
return 0;
|
|
4
|
+
if (typeof value === 'number')
|
|
5
|
+
return value;
|
|
6
|
+
if (value instanceof Date)
|
|
7
|
+
return value.getTime();
|
|
8
|
+
if (typeof value === 'object') {
|
|
9
|
+
const v = value;
|
|
10
|
+
if (typeof v.toMillis === 'function') {
|
|
11
|
+
return v.toMillis();
|
|
12
|
+
}
|
|
13
|
+
if (typeof v.toDate === 'function') {
|
|
14
|
+
return v.toDate().getTime();
|
|
15
|
+
}
|
|
16
|
+
if (typeof v['_seconds'] === 'number') {
|
|
17
|
+
return v['_seconds'] * 1000 +
|
|
18
|
+
Math.floor((v['_nanoseconds'] ?? 0) / 1e6);
|
|
19
|
+
}
|
|
20
|
+
if (typeof v['seconds'] === 'number') {
|
|
21
|
+
return v['seconds'] * 1000 +
|
|
22
|
+
Math.floor((v['nanoseconds'] ?? 0) / 1e6);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
function normalizeNavItems(raw) {
|
|
28
|
+
if (!Array.isArray(raw))
|
|
29
|
+
return [];
|
|
30
|
+
return raw.map((item) => ({
|
|
31
|
+
label: item['label'] ?? '',
|
|
32
|
+
url: item['url'] ?? '',
|
|
33
|
+
order: item['order'],
|
|
34
|
+
external: item['external'],
|
|
35
|
+
icon: item['icon'],
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
function normalizeSocialLinks(raw) {
|
|
39
|
+
if (!Array.isArray(raw))
|
|
40
|
+
return [];
|
|
41
|
+
return raw.map((item) => ({
|
|
42
|
+
platform: item['platform'],
|
|
43
|
+
url: item['url'] ?? '',
|
|
44
|
+
label: item['label'],
|
|
45
|
+
icon: item['icon'],
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
export function normalizeSiteConfig(raw) {
|
|
49
|
+
return {
|
|
50
|
+
id: raw['id'] ?? '',
|
|
51
|
+
siteName: raw['siteName'] ?? '',
|
|
52
|
+
siteUrl: raw['siteUrl'] ?? '',
|
|
53
|
+
description: raw['description'],
|
|
54
|
+
logo: raw['logo'],
|
|
55
|
+
favicon: raw['favicon'],
|
|
56
|
+
nav: normalizeNavItems(raw['nav']),
|
|
57
|
+
social: normalizeSocialLinks(raw['social']),
|
|
58
|
+
defaultAuthorId: raw['defaultAuthorId'],
|
|
59
|
+
updatedAt: normalizeTimestamp(raw['updatedAt']),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=normalize-site-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-site-config.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/utils/normalize-site-config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SiteConfig } from '../models/site-config.model';\nimport type { NavItem, SocialLink, SocialPlatform } from '../models/site-config.model';\n\nfunction normalizeTimestamp(value: unknown): number {\n if (value == null) return 0;\n if (typeof value === 'number') return value;\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'object') {\n const v = value as Record<string, unknown>;\n if (typeof (v as { toMillis?: unknown }).toMillis === 'function') {\n return (v as { toMillis(): number }).toMillis();\n }\n if (typeof (v as { toDate?: unknown }).toDate === 'function') {\n return (v as { toDate(): Date }).toDate().getTime();\n }\n if (typeof v['_seconds'] === 'number') {\n return (v['_seconds'] as number) * 1000 +\n Math.floor(((v['_nanoseconds'] as number) ?? 0) / 1e6);\n }\n if (typeof v['seconds'] === 'number') {\n return (v['seconds'] as number) * 1000 +\n Math.floor(((v['nanoseconds'] as number) ?? 0) / 1e6);\n }\n }\n return 0;\n}\n\nfunction normalizeNavItems(raw: unknown): NavItem[] {\n if (!Array.isArray(raw)) return [];\n return (raw as Record<string, unknown>[]).map((item) => ({\n label: (item['label'] as string) ?? '',\n url: (item['url'] as string) ?? '',\n order: item['order'] as number | undefined,\n external: item['external'] as boolean | undefined,\n icon: item['icon'] as string | undefined,\n }));\n}\n\nfunction normalizeSocialLinks(raw: unknown): SocialLink[] {\n if (!Array.isArray(raw)) return [];\n return (raw as Record<string, unknown>[]).map((item) => ({\n platform: item['platform'] as SocialPlatform,\n url: (item['url'] as string) ?? '',\n label: item['label'] as string | undefined,\n icon: item['icon'] as string | undefined,\n }));\n}\n\nexport function normalizeSiteConfig(raw: Record<string, unknown>): SiteConfig {\n return {\n id: (raw['id'] as string) ?? '',\n siteName: (raw['siteName'] as string) ?? '',\n siteUrl: (raw['siteUrl'] as string) ?? '',\n description: raw['description'] as string | undefined,\n logo: raw['logo'] as string | undefined,\n favicon: raw['favicon'] as string | undefined,\n nav: normalizeNavItems(raw['nav']),\n social: normalizeSocialLinks(raw['social']),\n defaultAuthorId: raw['defaultAuthorId'] as string | undefined,\n updatedAt: normalizeTimestamp(raw['updatedAt']),\n };\n}\n"]}
|
package/index.d.ts
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
export * from './lib/
|
|
1
|
+
export * from './lib/firebase/firebase.config';
|
|
2
|
+
export * from './lib/firebase/firebase.providers';
|
|
3
|
+
export * from './lib/models/post.model';
|
|
4
|
+
export * from './lib/models/site-config.model';
|
|
5
|
+
export * from './lib/models/page.model';
|
|
6
|
+
export * from './lib/models/tag.model';
|
|
7
|
+
export * from './lib/models/author.model';
|
|
8
|
+
export * from './lib/services/auth.service';
|
|
9
|
+
export * from './lib/services/post.service';
|
|
10
|
+
export * from './lib/services/page.service';
|
|
11
|
+
export * from './lib/services/site-config.service';
|
|
12
|
+
export * from './lib/services/tag.service';
|
|
13
|
+
export * from './lib/tokens/post-service.token';
|
|
14
|
+
export * from './lib/tokens/page-service.token';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
import type { FirebaseApp, FirebaseOptions } from 'firebase/app';
|
|
3
|
+
import type { Firestore } from 'firebase/firestore';
|
|
4
|
+
import type { FirebaseStorage } from 'firebase/storage';
|
|
5
|
+
import type { Auth } from 'firebase/auth';
|
|
6
|
+
export declare const FIREBASE_OPTIONS: InjectionToken<FirebaseOptions>;
|
|
7
|
+
export declare const FIREBASE_APP: InjectionToken<FirebaseApp>;
|
|
8
|
+
export declare const FIRESTORE: InjectionToken<Firestore>;
|
|
9
|
+
export declare const FIREBASE_STORAGE: InjectionToken<FirebaseStorage>;
|
|
10
|
+
export declare const FIREBASE_AUTH: InjectionToken<Auth | null>;
|
|
11
|
+
export declare const ADMIN_EMAIL: InjectionToken<string>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SocialLink } from './site-config.model';
|
|
2
|
+
export interface Author {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
slug: string;
|
|
6
|
+
bio?: string;
|
|
7
|
+
avatarUrl?: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
social?: SocialLink[];
|
|
10
|
+
/** Unix milliseconds. */
|
|
11
|
+
createdAt: number;
|
|
12
|
+
/** Unix milliseconds. */
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SeoMeta, EmbeddedMediaEntry } from './post.model';
|
|
2
|
+
import type { SocialPlatform } from './site-config.model';
|
|
3
|
+
export interface CmsPageBase {
|
|
4
|
+
id: string;
|
|
5
|
+
type: 'about' | 'links';
|
|
6
|
+
slug: string;
|
|
7
|
+
title: string;
|
|
8
|
+
status: 'draft' | 'published';
|
|
9
|
+
seo: SeoMeta;
|
|
10
|
+
/** Unix milliseconds — same convention as BlogPost. */
|
|
11
|
+
updatedAt: number;
|
|
12
|
+
/** Unix milliseconds. */
|
|
13
|
+
createdAt: number;
|
|
14
|
+
}
|
|
15
|
+
export interface AboutPage extends CmsPageBase {
|
|
16
|
+
type: 'about';
|
|
17
|
+
heroImageUrl?: string;
|
|
18
|
+
heroImageAlt?: string;
|
|
19
|
+
body: string;
|
|
20
|
+
contentVersion: number;
|
|
21
|
+
embeddedMedia: Record<string, EmbeddedMediaEntry>;
|
|
22
|
+
}
|
|
23
|
+
export interface LinksLink {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
url: string;
|
|
27
|
+
icon?: string;
|
|
28
|
+
platform?: SocialPlatform;
|
|
29
|
+
highlighted?: boolean;
|
|
30
|
+
order: number;
|
|
31
|
+
}
|
|
32
|
+
export interface LinksPage extends CmsPageBase {
|
|
33
|
+
type: 'links';
|
|
34
|
+
avatarUrl?: string;
|
|
35
|
+
avatarAlt?: string;
|
|
36
|
+
headline?: string;
|
|
37
|
+
bio?: string;
|
|
38
|
+
links: LinksLink[];
|
|
39
|
+
}
|
|
40
|
+
export type CmsPageUnion = AboutPage | LinksPage;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface SeoMeta {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
keywords?: string[];
|
|
5
|
+
ogImage?: string;
|
|
6
|
+
canonicalUrl?: string;
|
|
7
|
+
noIndex?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface EmbeddedMediaEntry {
|
|
10
|
+
token: string;
|
|
11
|
+
storagePath: string;
|
|
12
|
+
downloadUrl: string;
|
|
13
|
+
alt: string;
|
|
14
|
+
mimeType: string;
|
|
15
|
+
}
|
|
16
|
+
export interface BlogPost {
|
|
17
|
+
id: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
title: string;
|
|
20
|
+
subtitle?: string;
|
|
21
|
+
status: 'published' | 'draft' | 'scheduled' | 'archived';
|
|
22
|
+
content: string;
|
|
23
|
+
excerpt?: string;
|
|
24
|
+
thumbnailUrl?: string;
|
|
25
|
+
thumbnailAlt?: string;
|
|
26
|
+
tags: string[];
|
|
27
|
+
authorId?: string;
|
|
28
|
+
readingTimeMinutes?: number;
|
|
29
|
+
embeddedMedia: Record<string, EmbeddedMediaEntry>;
|
|
30
|
+
seo: SeoMeta;
|
|
31
|
+
/** Unix milliseconds. Stored as Firestore Timestamp but always normalized on read. */
|
|
32
|
+
publishedAt: number;
|
|
33
|
+
/** Unix milliseconds, optional. */
|
|
34
|
+
scheduledPublishAt?: number;
|
|
35
|
+
/** Unix milliseconds. */
|
|
36
|
+
updatedAt: number;
|
|
37
|
+
/** Unix milliseconds. */
|
|
38
|
+
createdAt: number;
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface NavItem {
|
|
2
|
+
label: string;
|
|
3
|
+
url: string;
|
|
4
|
+
order?: number;
|
|
5
|
+
external?: boolean;
|
|
6
|
+
icon?: string;
|
|
7
|
+
}
|
|
8
|
+
export type SocialPlatform = 'twitter' | 'instagram' | 'github' | 'linkedin' | 'youtube' | 'twitch' | 'bluesky' | 'tiktok' | 'facebook' | 'email' | 'website';
|
|
9
|
+
export interface SocialLink {
|
|
10
|
+
platform: SocialPlatform;
|
|
11
|
+
url: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
icon?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SiteConfig {
|
|
16
|
+
id: string;
|
|
17
|
+
siteName: string;
|
|
18
|
+
siteUrl: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
logo?: string;
|
|
21
|
+
favicon?: string;
|
|
22
|
+
nav: NavItem[];
|
|
23
|
+
social: SocialLink[];
|
|
24
|
+
defaultAuthorId?: string;
|
|
25
|
+
/** Unix milliseconds. */
|
|
26
|
+
updatedAt: number;
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { User } from 'firebase/auth';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class AuthService {
|
|
4
|
+
private readonly auth;
|
|
5
|
+
private readonly adminEmail;
|
|
6
|
+
readonly user: import("@angular/core").Signal<User | null | undefined>;
|
|
7
|
+
readonly isAuthenticated: import("@angular/core").Signal<boolean>;
|
|
8
|
+
readonly isAdmin: import("@angular/core").Signal<boolean>;
|
|
9
|
+
signInWithGoogle(): Promise<void>;
|
|
10
|
+
signOut(): Promise<void>;
|
|
11
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AuthService, never>;
|
|
12
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<AuthService>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import type { CmsPageUnion } from '../models/page.model';
|
|
3
|
+
import type { IPageService } from '../tokens/page-service.token';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
export declare class PageService implements IPageService {
|
|
6
|
+
private readonly firestore;
|
|
7
|
+
private readonly storage;
|
|
8
|
+
getPageBySlug(slug: string): Observable<CmsPageUnion | null>;
|
|
9
|
+
getAllPages(): Observable<CmsPageUnion[]>;
|
|
10
|
+
getPageById(id: string): Observable<CmsPageUnion>;
|
|
11
|
+
deleteStorageFile(storagePath: string): Observable<void>;
|
|
12
|
+
savePage(page: CmsPageUnion): Observable<CmsPageUnion>;
|
|
13
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PageService, never>;
|
|
14
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<PageService>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import type { BlogPost } from '../models/post.model';
|
|
3
|
+
import type { IBlogPostService } from '../tokens/post-service.token';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
export declare class PostService implements IBlogPostService {
|
|
6
|
+
private readonly firestore;
|
|
7
|
+
private readonly storage;
|
|
8
|
+
getPublishedPosts(): Observable<BlogPost[]>;
|
|
9
|
+
getPostBySlug(slug: string): Observable<BlogPost | null>;
|
|
10
|
+
getPostsByTag(tag: string): Observable<BlogPost[]>;
|
|
11
|
+
getAllPosts(): Observable<BlogPost[]>;
|
|
12
|
+
getPostById(id: string): Observable<BlogPost>;
|
|
13
|
+
deleteStorageFile(storagePath: string): Observable<void>;
|
|
14
|
+
savePost(post: BlogPost): Observable<BlogPost>;
|
|
15
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PostService, never>;
|
|
16
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<PostService>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import type { SiteConfig } from '../models/site-config.model';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export declare class SiteConfigService {
|
|
5
|
+
private readonly firestore;
|
|
6
|
+
getSiteConfig(siteId: string): Observable<SiteConfig | null>;
|
|
7
|
+
getDefaultSiteConfig(): Observable<SiteConfig | null>;
|
|
8
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SiteConfigService, never>;
|
|
9
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<SiteConfigService>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import type { Tag } from '../models/tag.model';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export declare class TagService {
|
|
5
|
+
private readonly firestore;
|
|
6
|
+
getAllTags(): Observable<Tag[]>;
|
|
7
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TagService, never>;
|
|
8
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<TagService>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
import type { Observable } from 'rxjs';
|
|
3
|
+
import type { CmsPageUnion } from '../models/page.model';
|
|
4
|
+
export interface IPageService {
|
|
5
|
+
getPageBySlug(slug: string): Observable<CmsPageUnion | null>;
|
|
6
|
+
getPageById(id: string): Observable<CmsPageUnion | null>;
|
|
7
|
+
}
|
|
8
|
+
export declare const BLOG_PAGE_SERVICE: InjectionToken<IPageService>;
|
|
9
|
+
export declare const PAGE_TRANSFER_KEY: import("@angular/core").StateKey<CmsPageUnion | null>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
import type { Observable } from 'rxjs';
|
|
3
|
+
import type { BlogPost } from '../models/post.model';
|
|
4
|
+
export interface IBlogPostService {
|
|
5
|
+
getPublishedPosts(): Observable<BlogPost[]>;
|
|
6
|
+
getPostBySlug(slug: string): Observable<BlogPost | null>;
|
|
7
|
+
}
|
|
8
|
+
export declare const BLOG_POST_SERVICE: InjectionToken<IBlogPostService>;
|
|
9
|
+
export declare const POST_TRANSFER_KEY: import("@angular/core").StateKey<BlogPost | null>;
|
|
10
|
+
export declare const POSTS_TRANSFER_KEY: import("@angular/core").StateKey<BlogPost[]>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BlogPost } from '../models/post.model';
|
|
2
|
+
/**
|
|
3
|
+
* Maps raw Firestore DocumentData (merged with its doc id) into a fully typed,
|
|
4
|
+
* TransferState-serializable BlogPost. Callers must pass { id: doc.id, ...doc.data() }.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizePost(raw: Record<string, unknown>): BlogPost;
|
package/package.json
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foliokit/cms-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core Firebase services, models, and tokens for FolioKit CMS",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"cms",
|
|
8
|
+
"foliokit",
|
|
9
|
+
"firebase",
|
|
10
|
+
"firestore"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/dougwilliamson/foliokit"
|
|
16
|
+
},
|
|
4
17
|
"publishConfig": {
|
|
5
18
|
"access": "public"
|
|
6
19
|
},
|
|
7
20
|
"peerDependencies": {
|
|
8
|
-
"@angular/common": "^21.
|
|
9
|
-
"@angular/core": "^21.
|
|
21
|
+
"@angular/common": "^21.2.4",
|
|
22
|
+
"@angular/core": "^21.2.4",
|
|
23
|
+
"firebase": "^11.10.0",
|
|
24
|
+
"rxjs": "~7.8.0"
|
|
10
25
|
},
|
|
11
26
|
"sideEffects": false,
|
|
12
27
|
"module": "esm2022/foliokit-cms-core.js",
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import * as i0 from "@angular/core";
|
|
3
|
-
export class CmsCore {
|
|
4
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CmsCore, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.6", type: CmsCore, isStandalone: true, selector: "lib-cms-core", ngImport: i0, template: "<p>CmsCore works!</p>\n", styles: [""] });
|
|
6
|
-
}
|
|
7
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CmsCore, decorators: [{
|
|
8
|
-
type: Component,
|
|
9
|
-
args: [{ selector: 'lib-cms-core', imports: [], template: "<p>CmsCore works!</p>\n" }]
|
|
10
|
-
}] });
|
|
11
|
-
//# sourceMappingURL=cms-core.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cms-core.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/cms-core/cms-core.ts","../../../../../../libs/cms-core/src/lib/cms-core/cms-core.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;;AAQ1C,MAAM,OAAO,OAAO;uGAAP,OAAO;2FAAP,OAAO,wECRpB,yBACA;;2FDOa,OAAO;kBANnB,SAAS;+BACE,cAAc,WACf,EAAE","sourcesContent":["import { Component } from '@angular/core';\n\n@Component({\n selector: 'lib-cms-core',\n imports: [],\n templateUrl: './cms-core.html',\n styleUrl: './cms-core.scss',\n})\nexport class CmsCore {}\n","<p>CmsCore works!</p>\n"]}
|