@foliokit/cms-core 0.4.2 → 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 CHANGED
@@ -8,8 +8,9 @@ export * from './lib/models/tag.model';
8
8
  export * from './lib/models/author.model';
9
9
  export * from './lib/services/auth.service';
10
10
  export * from './lib/services/post.service';
11
+ export * from './lib/services/page.service';
11
12
  export * from './lib/services/site-config.service';
12
13
  export * from './lib/services/tag.service';
13
14
  export * from './lib/tokens/post-service.token';
14
- export * from './lib/utils/normalize-post';
15
+ export * from './lib/tokens/page-service.token';
15
16
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
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,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,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/site-config.service';\nexport * from './lib/services/tag.service';\nexport * from './lib/tokens/post-service.token';\nexport * from './lib/utils/normalize-post';\n"]}
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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"author.model.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/models/author.model.ts"],"names":[],"mappings":"","sourcesContent":["import type { Timestamp } from 'firebase/firestore';\nimport 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 createdAt: Timestamp;\n updatedAt: Timestamp;\n}\n"]}
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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"page.model.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/models/page.model.ts"],"names":[],"mappings":"","sourcesContent":["import type { Timestamp } from 'firebase/firestore';\nimport type { SeoMeta } from './post.model';\n\nexport interface CmsPage {\n id: string;\n slug: string;\n title: string;\n status: 'published' | 'draft';\n content?: string;\n seo: SeoMeta;\n createdAt: Timestamp;\n updatedAt: Timestamp;\n}\n\nexport interface LinktreeLink {\n label: string;\n url: string;\n icon?: string;\n order: number;\n}\n\nexport interface HomePage extends CmsPage {\n heroHeadline?: string;\n heroSubheadline?: string;\n heroCta?: { label: string; href: string };\n featuredPostIds?: string[];\n}\n\nexport interface AboutPage extends CmsPage {\n bio?: string;\n skills?: string[];\n avatarUrl?: string;\n}\n\nexport interface LinktreePage extends CmsPage {\n links: LinktreeLink[];\n avatarUrl?: string;\n displayName?: string;\n}\n"]}
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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"site-config.model.js","sourceRoot":"","sources":["../../../../../../libs/cms-core/src/lib/models/site-config.model.ts"],"names":[],"mappings":"","sourcesContent":["import type { Timestamp } from 'firebase/firestore';\n\nexport interface NavItem {\n label: string;\n url: string;\n order?: number;\n external?: boolean;\n icon?: string;\n}\n\nexport interface SocialLink {\n platform: string;\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 updatedAt: Timestamp;\n}\n"]}
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,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"]}
@@ -1 +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';\n\n@Injectable({ providedIn: 'root' })\nexport class PostService {\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"]}
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"]}
@@ -3,6 +3,7 @@ import { doc, getDoc } from 'firebase/firestore';
3
3
  import { from, of } from 'rxjs';
4
4
  import { catchError, map } from 'rxjs/operators';
5
5
  import { FIRESTORE } from '../firebase/firebase.config';
6
+ import { normalizeSiteConfig } from '../utils/normalize-site-config';
6
7
  import * as i0 from "@angular/core";
7
8
  export class SiteConfigService {
8
9
  firestore = inject(FIRESTORE);
@@ -11,7 +12,7 @@ export class SiteConfigService {
11
12
  return from(getDoc(ref)).pipe(map((snap) => {
12
13
  if (!snap.exists())
13
14
  return null;
14
- return { id: snap.id, ...snap.data() };
15
+ return normalizeSiteConfig({ id: snap.id, ...snap.data() });
15
16
  }), catchError((err) => {
16
17
  console.error('[SiteConfigService.getSiteConfig]', err);
17
18
  return of(null);
@@ -1 +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;;AAIxD,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,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAgB,CAAC;QACvD,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';\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 { id: snap.id, ...snap.data() } as SiteConfig;\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"]}
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,4 @@
1
+ import { InjectionToken, makeStateKey } from '@angular/core';
2
+ export const BLOG_PAGE_SERVICE = new InjectionToken('BLOG_PAGE_SERVICE');
3
+ export const PAGE_TRANSFER_KEY = makeStateKey('cms-page');
4
+ //# sourceMappingURL=page-service.token.js.map
@@ -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,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,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
@@ -7,7 +7,8 @@ export * from './lib/models/tag.model';
7
7
  export * from './lib/models/author.model';
8
8
  export * from './lib/services/auth.service';
9
9
  export * from './lib/services/post.service';
10
+ export * from './lib/services/page.service';
10
11
  export * from './lib/services/site-config.service';
11
12
  export * from './lib/services/tag.service';
12
13
  export * from './lib/tokens/post-service.token';
13
- export * from './lib/utils/normalize-post';
14
+ export * from './lib/tokens/page-service.token';
@@ -1,4 +1,3 @@
1
- import type { Timestamp } from 'firebase/firestore';
2
1
  import type { SocialLink } from './site-config.model';
3
2
  export interface Author {
4
3
  id: string;
@@ -8,6 +7,8 @@ export interface Author {
8
7
  avatarUrl?: string;
9
8
  email?: string;
10
9
  social?: SocialLink[];
11
- createdAt: Timestamp;
12
- updatedAt: Timestamp;
10
+ /** Unix milliseconds. */
11
+ createdAt: number;
12
+ /** Unix milliseconds. */
13
+ updatedAt: number;
13
14
  }
@@ -1,37 +1,40 @@
1
- import type { Timestamp } from 'firebase/firestore';
2
- import type { SeoMeta } from './post.model';
3
- export interface CmsPage {
1
+ import type { SeoMeta, EmbeddedMediaEntry } from './post.model';
2
+ import type { SocialPlatform } from './site-config.model';
3
+ export interface CmsPageBase {
4
4
  id: string;
5
+ type: 'about' | 'links';
5
6
  slug: string;
6
7
  title: string;
7
- status: 'published' | 'draft';
8
- content?: string;
8
+ status: 'draft' | 'published';
9
9
  seo: SeoMeta;
10
- createdAt: Timestamp;
11
- updatedAt: Timestamp;
10
+ /** Unix milliseconds — same convention as BlogPost. */
11
+ updatedAt: number;
12
+ /** Unix milliseconds. */
13
+ createdAt: number;
12
14
  }
13
- export interface LinktreeLink {
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;
14
25
  label: string;
15
26
  url: string;
16
27
  icon?: string;
28
+ platform?: SocialPlatform;
29
+ highlighted?: boolean;
17
30
  order: number;
18
31
  }
19
- export interface HomePage extends CmsPage {
20
- heroHeadline?: string;
21
- heroSubheadline?: string;
22
- heroCta?: {
23
- label: string;
24
- href: string;
25
- };
26
- featuredPostIds?: string[];
27
- }
28
- export interface AboutPage extends CmsPage {
29
- bio?: string;
30
- skills?: string[];
32
+ export interface LinksPage extends CmsPageBase {
33
+ type: 'links';
31
34
  avatarUrl?: string;
35
+ avatarAlt?: string;
36
+ headline?: string;
37
+ bio?: string;
38
+ links: LinksLink[];
32
39
  }
33
- export interface LinktreePage extends CmsPage {
34
- links: LinktreeLink[];
35
- avatarUrl?: string;
36
- displayName?: string;
37
- }
40
+ export type CmsPageUnion = AboutPage | LinksPage;
@@ -1,4 +1,3 @@
1
- import type { Timestamp } from 'firebase/firestore';
2
1
  export interface NavItem {
3
2
  label: string;
4
3
  url: string;
@@ -6,8 +5,9 @@ export interface NavItem {
6
5
  external?: boolean;
7
6
  icon?: string;
8
7
  }
8
+ export type SocialPlatform = 'twitter' | 'instagram' | 'github' | 'linkedin' | 'youtube' | 'twitch' | 'bluesky' | 'tiktok' | 'facebook' | 'email' | 'website';
9
9
  export interface SocialLink {
10
- platform: string;
10
+ platform: SocialPlatform;
11
11
  url: string;
12
12
  label?: string;
13
13
  icon?: string;
@@ -22,5 +22,6 @@ export interface SiteConfig {
22
22
  nav: NavItem[];
23
23
  social: SocialLink[];
24
24
  defaultAuthorId?: string;
25
- updatedAt: Timestamp;
25
+ /** Unix milliseconds. */
26
+ updatedAt: number;
26
27
  }
@@ -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
+ }
@@ -1,7 +1,8 @@
1
1
  import { Observable } from 'rxjs';
2
2
  import type { BlogPost } from '../models/post.model';
3
+ import type { IBlogPostService } from '../tokens/post-service.token';
3
4
  import * as i0 from "@angular/core";
4
- export declare class PostService {
5
+ export declare class PostService implements IBlogPostService {
5
6
  private readonly firestore;
6
7
  private readonly storage;
7
8
  getPublishedPosts(): Observable<BlogPost[]>;
@@ -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,2 @@
1
+ import type { CmsPageUnion } from '../models/page.model';
2
+ export declare function normalizePage(raw: Record<string, unknown>): CmsPageUnion;
@@ -0,0 +1,2 @@
1
+ import type { SiteConfig } from '../models/site-config.model';
2
+ export declare function normalizeSiteConfig(raw: Record<string, unknown>): SiteConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foliokit/cms-core",
3
- "version": "0.4.2",
3
+ "version": "1.0.0",
4
4
  "description": "Core Firebase services, models, and tokens for FolioKit CMS",
5
5
  "keywords": [
6
6
  "angular",