@stacksjs/cms 0.74.46 → 0.74.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authors/destroy.d.ts +1 -1
- package/dist/authors/fetch.d.ts +1 -1
- package/dist/authors/store.d.ts +1 -1
- package/dist/authors/update.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/commentables/fetch.js +1 -1
- package/dist/commentables/store.js +1 -1
- package/dist/database.d.ts +1 -1
- package/dist/database.js +1 -1
- package/dist/menus/index.js +1 -1
- package/dist/pages/document.js +1 -1
- package/dist/pages/fetch.d.ts +1 -1
- package/dist/pages/update.d.ts +1 -1
- package/dist/taggables/fetch.js +1 -1
- package/package.json +8 -8
package/dist/authors/fetch.d.ts
CHANGED
package/dist/authors/store.d.ts
CHANGED
package/dist/authors/update.d.ts
CHANGED
package/dist/build.js
CHANGED
|
@@ -23,7 +23,7 @@ import{copyFileSync,existsSync,mkdirSync,readdirSync,writeFileSync}from"node:fs"
|
|
|
23
23
|
font-weight: 400;
|
|
24
24
|
font-style: normal;
|
|
25
25
|
font-display: swap;
|
|
26
|
-
}`}function copyBlogFonts(outDir){const sourceDir=p.frameworkPath("defaults/resources/assets/fonts/nps");if(!existsSync(sourceDir))return;const targetDir=join(outDir,"assets","fonts","nps");ensureDir(targetDir);for(const file of readdirSync(sourceDir))if(file.endsWith(".woff2"))copyFileSync(join(sourceDir,file),join(targetDir,file))}function copyBlogImages(outDir){const sourceDir=p.frameworkPath("defaults/resources/assets/images");if(!existsSync(sourceDir))return;const targetDir=join(outDir,"assets","images");ensureDir(targetDir);for(const file of readdirSync(sourceDir))if(file.endsWith(".svg"))copyFileSync(join(sourceDir,file),join(targetDir,file))}function sqliteDatabasePath(){const configuredPath=process.env.DB_DATABASE_PATH||"database/stacks.sqlite";return configuredPath.startsWith("/")?configuredPath:p.projectPath(configuredPath)}function shouldUseLocalSqlite(){return(process.env.DB_CONNECTION||"sqlite")==="sqlite"&&existsSync(sqliteDatabasePath())}async function queryLocalSqlite(sql){if(!shouldUseLocalSqlite())return null;const{Database}=await import("bun:sqlite"),sqlite=new Database(sqliteDatabasePath(),{readonly:!0});try{return sqlite.query(sql).all()}finally{sqlite.close()}}async function withTimeout(promise,ms){let timeout;try{return await Promise.race([promise,new Promise((_,reject)=>{timeout=setTimeout(()=>reject(Error(`CMS database query timed out after ${ms}ms`)),ms)})])}finally{if(timeout)clearTimeout(timeout)}}async function fetchPublishedPosts(){const sqlitePosts=await queryLocalSqlite("select * from posts where status = 'published' order by published_at desc");if(sqlitePosts)return sqlitePosts;const{db}=await import("@stacksjs/database");return await withTimeout(db.selectFrom("posts").where("status","=","published").orderBy("published_at","desc").selectAll().execute(),3000)}async function fetchAuthors(){const sqliteAuthors=await queryLocalSqlite("select * from authors order by created_at desc");if(sqliteAuthors)return sqliteAuthors;const{db}=await import("@stacksjs/database");return await withTimeout(db.selectFrom("authors").selectAll().execute(),3000)}function generateLayout(config,title,content,_options){return`<!DOCTYPE html>
|
|
26
|
+
}`}function copyBlogFonts(outDir){const sourceDir=p.frameworkPath("defaults/resources/assets/fonts/nps");if(!existsSync(sourceDir))return;const targetDir=join(outDir,"assets","fonts","nps");ensureDir(targetDir);for(const file of readdirSync(sourceDir))if(file.endsWith(".woff2"))copyFileSync(join(sourceDir,file),join(targetDir,file))}function copyBlogImages(outDir){const sourceDir=p.frameworkPath("defaults/resources/assets/images");if(!existsSync(sourceDir))return;const targetDir=join(outDir,"assets","images");ensureDir(targetDir);for(const file of readdirSync(sourceDir))if(file.endsWith(".svg"))copyFileSync(join(sourceDir,file),join(targetDir,file))}function sqliteDatabasePath(){const configuredPath=process.env.DB_DATABASE_PATH||"database/stacks.sqlite";return configuredPath.startsWith("/")?configuredPath:p.projectPath(configuredPath)}function shouldUseLocalSqlite(){return(process.env.DB_CONNECTION||"sqlite")==="sqlite"&&existsSync(sqliteDatabasePath())}async function queryLocalSqlite(sql){if(!shouldUseLocalSqlite())return null;const{Database}=await import("bun:sqlite"),sqlite=new Database(sqliteDatabasePath(),{readonly:!0});try{return sqlite.query(sql).all()}finally{sqlite.close()}}async function withTimeout(promise,ms){let timeout;try{return await Promise.race([promise,new Promise((_,reject)=>{timeout=setTimeout(()=>reject(Error(`CMS database query timed out after ${ms}ms`)),ms)})])}finally{if(timeout)clearTimeout(timeout)}}async function fetchPublishedPosts(){const sqlitePosts=await queryLocalSqlite("select * from posts where status = 'published' order by published_at desc");if(sqlitePosts)return sqlitePosts;const{db}=await import("@stacksjs/database/runtime");return await withTimeout(db.selectFrom("posts").where("status","=","published").orderBy("published_at","desc").selectAll().execute(),3000)}async function fetchAuthors(){const sqliteAuthors=await queryLocalSqlite("select * from authors order by created_at desc");if(sqliteAuthors)return sqliteAuthors;const{db}=await import("@stacksjs/database/runtime");return await withTimeout(db.selectFrom("authors").selectAll().execute(),3000)}function generateLayout(config,title,content,_options){return`<!DOCTYPE html>
|
|
27
27
|
<html lang="en">
|
|
28
28
|
<head>
|
|
29
29
|
<meta charset="UTF-8">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{sqlDateTime}from"@stacksjs/database";import{getDb}from"../database";import{formatDate}from"@stacksjs/orm";export async function fetchComments(options={}){let query=(await getDb()).selectFrom("commentables");if(options.status)query=query.where("status","=",options.status);if(options.commentables_id)query=query.where("commentables_id","=",options.commentables_id);if(options.commentables_type)query=query.where("commentables_type","=",options.commentables_type);if(options.limit)query=query.limit(options.limit);if(options.offset)query=query.offset(options.offset);return query.selectAll().execute()}export async function fetchCommentById(id){return(await getDb()).selectFrom("commentables").where("id","=",id).selectAll().executeTakeFirst()}export async function fetchCommentsByCommentables(commentables_id,commentables_type,options={}){let query=(await getDb()).selectFrom("commentables").where("commentables_id","=",commentables_id).where("commentables_type","=",commentables_type);if(options.status)query=query.where("status","=",options.status);if(options.limit)query=query.limit(options.limit);if(options.offset)query=query.offset(options.offset);return query.selectAll().execute()}export async function fetchCommentCountByPeriod(days){const db=await getDb();try{const result=await db.selectFrom("commentables").where("created_at",">=",sqlDateTime(new Date(Date.now()-days*24*60*60*1000))).count();return Number(result)||0}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch comment count: ${error.message}`);throw error}}export async function fetchCommentsByStatus(status,options={}){const db=await getDb();try{let query=db.selectFrom("commentables").where("status","=",status);if(options.limit)query=query.limit(options.limit);if(options.offset)query=query.offset(options.offset);return query.selectAll().execute()}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch comments by status: ${error.message}`);throw error}}export async function calculateApprovalRate(){const db=await getDb();try{const[approvedCount,totalCount]=await Promise.all([db.selectFrom("commentables").where("status","=","approved").count(),db.selectFrom("commentables").count()]),approved=Number(approvedCount||0),total=Number(totalCount||0),rate=total>0?approved/total*100:0;return{approved,total,rate}}catch(error){if(error instanceof Error)throw TypeError(`Failed to calculate approval rate: ${error.message}`);throw error}}export async function fetchPostsWithMostComments(dateRange,options={}){const db=await getDb();try{let query=db.selectFrom("posts").leftJoin("commentables","posts.id","=","commentables.commentables_id").where("commentables.created_at",">=",formatDate(dateRange.startDate)).where("commentables.created_at","<=",formatDate(dateRange.endDate)).select(["posts.id","posts.title"]).groupBy("posts.id","posts.title").orderBy("posts.id","desc");if(options.limit)query=query.limit(options.limit);return(await query.execute()).map((row)=>({id:Number(row.id),title:String(row.title),comment_count:Number(row.comment_count||0)}))}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch posts with most comments: ${error.message}`);throw error}}export async function fetchCommentCountBarGraph(dateRange,options={}){try{const posts=await fetchPostsWithMostComments(dateRange,options);return{labels:posts.map((post)=>post.title),values:posts.map((post)=>post.comment_count)}}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch bar graph data: ${error.message}`);throw error}}export async function fetchStatusDistributionDonut(dateRange){const db=await getDb();try{const typedResults=await db.selectFrom("commentables").where("created_at",">=",formatDate(dateRange.startDate)).where("created_at","<=",formatDate(dateRange.endDate)).select(["status"]).groupBy("status").execute(),total=typedResults.reduce((sum,row)=>sum+Number(row.count||0),0),labels=typedResults.map((row)=>String(row.status)),values=typedResults.map((row)=>Number(row.count||0)),percentages=values.map((value)=>total>0?value/total*100:0);return{labels,values,percentages}}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch status distribution: ${error.message}`);throw error}}export async function fetchMonthlyCommentCounts(dateRange){const db=await getDb();try{const results=await db.selectFrom("commentables").where("created_at",">=",formatDate(dateRange.startDate)).where("created_at","<=",formatDate(dateRange.endDate)).select(["created_at"]).groupBy("created_at").orderBy("created_at","asc").execute(),monthlyCounts=new Map;results.forEach((row)=>{if(!row.created_at)return;const date=new Date(row.created_at),key=`${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,"0")}`;monthlyCounts.set(key,(monthlyCounts.get(key)||0)+Number(row.count||0))});const labels=[],values=[];monthlyCounts.forEach((count,key)=>{const[year,month]=key.split("-");labels.push(new Date(Number(year),Number(month)-1).toLocaleString("default",{month:"short",year:"numeric"}));values.push(count)});return{labels,values}}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch monthly comment counts: ${error.message}`);throw error}}
|
|
1
|
+
import{sqlDateTime}from"@stacksjs/database/runtime";import{getDb}from"../database";import{formatDate}from"@stacksjs/orm";export async function fetchComments(options={}){let query=(await getDb()).selectFrom("commentables");if(options.status)query=query.where("status","=",options.status);if(options.commentables_id)query=query.where("commentables_id","=",options.commentables_id);if(options.commentables_type)query=query.where("commentables_type","=",options.commentables_type);if(options.limit)query=query.limit(options.limit);if(options.offset)query=query.offset(options.offset);return query.selectAll().execute()}export async function fetchCommentById(id){return(await getDb()).selectFrom("commentables").where("id","=",id).selectAll().executeTakeFirst()}export async function fetchCommentsByCommentables(commentables_id,commentables_type,options={}){let query=(await getDb()).selectFrom("commentables").where("commentables_id","=",commentables_id).where("commentables_type","=",commentables_type);if(options.status)query=query.where("status","=",options.status);if(options.limit)query=query.limit(options.limit);if(options.offset)query=query.offset(options.offset);return query.selectAll().execute()}export async function fetchCommentCountByPeriod(days){const db=await getDb();try{const result=await db.selectFrom("commentables").where("created_at",">=",sqlDateTime(new Date(Date.now()-days*24*60*60*1000))).count();return Number(result)||0}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch comment count: ${error.message}`);throw error}}export async function fetchCommentsByStatus(status,options={}){const db=await getDb();try{let query=db.selectFrom("commentables").where("status","=",status);if(options.limit)query=query.limit(options.limit);if(options.offset)query=query.offset(options.offset);return query.selectAll().execute()}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch comments by status: ${error.message}`);throw error}}export async function calculateApprovalRate(){const db=await getDb();try{const[approvedCount,totalCount]=await Promise.all([db.selectFrom("commentables").where("status","=","approved").count(),db.selectFrom("commentables").count()]),approved=Number(approvedCount||0),total=Number(totalCount||0),rate=total>0?approved/total*100:0;return{approved,total,rate}}catch(error){if(error instanceof Error)throw TypeError(`Failed to calculate approval rate: ${error.message}`);throw error}}export async function fetchPostsWithMostComments(dateRange,options={}){const db=await getDb();try{let query=db.selectFrom("posts").leftJoin("commentables","posts.id","=","commentables.commentables_id").where("commentables.created_at",">=",formatDate(dateRange.startDate)).where("commentables.created_at","<=",formatDate(dateRange.endDate)).select(["posts.id","posts.title"]).groupBy("posts.id","posts.title").orderBy("posts.id","desc");if(options.limit)query=query.limit(options.limit);return(await query.execute()).map((row)=>({id:Number(row.id),title:String(row.title),comment_count:Number(row.comment_count||0)}))}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch posts with most comments: ${error.message}`);throw error}}export async function fetchCommentCountBarGraph(dateRange,options={}){try{const posts=await fetchPostsWithMostComments(dateRange,options);return{labels:posts.map((post)=>post.title),values:posts.map((post)=>post.comment_count)}}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch bar graph data: ${error.message}`);throw error}}export async function fetchStatusDistributionDonut(dateRange){const db=await getDb();try{const typedResults=await db.selectFrom("commentables").where("created_at",">=",formatDate(dateRange.startDate)).where("created_at","<=",formatDate(dateRange.endDate)).select(["status"]).groupBy("status").execute(),total=typedResults.reduce((sum,row)=>sum+Number(row.count||0),0),labels=typedResults.map((row)=>String(row.status)),values=typedResults.map((row)=>Number(row.count||0)),percentages=values.map((value)=>total>0?value/total*100:0);return{labels,values,percentages}}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch status distribution: ${error.message}`);throw error}}export async function fetchMonthlyCommentCounts(dateRange){const db=await getDb();try{const results=await db.selectFrom("commentables").where("created_at",">=",formatDate(dateRange.startDate)).where("created_at","<=",formatDate(dateRange.endDate)).select(["created_at"]).groupBy("created_at").orderBy("created_at","asc").execute(),monthlyCounts=new Map;results.forEach((row)=>{if(!row.created_at)return;const date=new Date(row.created_at),key=`${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,"0")}`;monthlyCounts.set(key,(monthlyCounts.get(key)||0)+Number(row.count||0))});const labels=[],values=[];monthlyCounts.forEach((count,key)=>{const[year,month]=key.split("-");labels.push(new Date(Number(year),Number(month)-1).toLocaleString("default",{month:"short",year:"numeric"}));values.push(count)});return{labels,values}}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch monthly comment counts: ${error.message}`);throw error}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{sqlDateTime}from"@stacksjs/database";import{getDb}from"../database";import{resolveWrittenRow}from"../results";async function readComment(db,id){const row=await db.selectFrom("commentables").where("id","=",id).selectAll().executeTakeFirst();if(!row)throw Error(`Comment ${id} not found after write`);return row}export async function createComment(data){const db=await getDb(),now=new Date,commentData={title:data.title,body:data.body,commentables_id:data.commentables_id,commentables_type:data.commentables_type,status:"pending",created_at:sqlDateTime(now)},written=await db.insertInto("commentables").values(commentData).returningAll().executeTakeFirst(),comment=await resolveWrittenRow(db,"commentables",written);if(!comment)throw Error("Failed to create comment");return comment}export async function updateComment(id,input){const db=await getDb();await db.updateTable("commentables").set({...input,updated_at:sqlDateTime()}).where("id","=",id).execute();return readComment(db,id)}export async function approveComment(id){const db=await getDb();await db.updateTable("commentables").set({status:"approved",approved_at:Date.now(),updated_at:sqlDateTime()}).where("id","=",id).execute();return readComment(db,id)}export async function rejectComment(id){const db=await getDb();await db.updateTable("commentables").set({status:"rejected",rejected_at:Date.now(),updated_at:sqlDateTime()}).where("id","=",id).execute();return readComment(db,id)}export async function deleteComment(id){await(await getDb()).deleteFrom("commentables").where("id","=",id).execute()}export async function store(data){const db=await getDb();try{if(!data.title||data.title.trim()==="")throw Error("Comment title is required");if(!data.body||data.body.trim()==="")throw Error("Comment body is required");if(!data.commentables_type||data.commentables_type.trim()==="")throw Error("Comment commentables_type is required");const validStatuses=["pending","approved","rejected"];if(data.status&&!validStatuses.includes(data.status))throw Error(`Invalid comment status: ${data.status}`);const commentData={title:data.title,body:data.body,status:data.status,commentables_id:data.commentables_id,commentables_type:data.commentables_type,user_id:data.user_id,is_active:data.is_active},written=await db.insertInto("commentables").values(commentData).returningAll().executeTakeFirst(),result=await resolveWrittenRow(db,"commentables",written);if(!result)throw Error("Failed to create comment");return result}catch(error){if(error instanceof Error)throw TypeError(`Failed to store comment: ${error.message}`);throw error}}
|
|
1
|
+
import{sqlDateTime}from"@stacksjs/database/runtime";import{getDb}from"../database";import{resolveWrittenRow}from"../results";async function readComment(db,id){const row=await db.selectFrom("commentables").where("id","=",id).selectAll().executeTakeFirst();if(!row)throw Error(`Comment ${id} not found after write`);return row}export async function createComment(data){const db=await getDb(),now=new Date,commentData={title:data.title,body:data.body,commentables_id:data.commentables_id,commentables_type:data.commentables_type,status:"pending",created_at:sqlDateTime(now)},written=await db.insertInto("commentables").values(commentData).returningAll().executeTakeFirst(),comment=await resolveWrittenRow(db,"commentables",written);if(!comment)throw Error("Failed to create comment");return comment}export async function updateComment(id,input){const db=await getDb();await db.updateTable("commentables").set({...input,updated_at:sqlDateTime()}).where("id","=",id).execute();return readComment(db,id)}export async function approveComment(id){const db=await getDb();await db.updateTable("commentables").set({status:"approved",approved_at:Date.now(),updated_at:sqlDateTime()}).where("id","=",id).execute();return readComment(db,id)}export async function rejectComment(id){const db=await getDb();await db.updateTable("commentables").set({status:"rejected",rejected_at:Date.now(),updated_at:sqlDateTime()}).where("id","=",id).execute();return readComment(db,id)}export async function deleteComment(id){await(await getDb()).deleteFrom("commentables").where("id","=",id).execute()}export async function store(data){const db=await getDb();try{if(!data.title||data.title.trim()==="")throw Error("Comment title is required");if(!data.body||data.body.trim()==="")throw Error("Comment body is required");if(!data.commentables_type||data.commentables_type.trim()==="")throw Error("Comment commentables_type is required");const validStatuses=["pending","approved","rejected"];if(data.status&&!validStatuses.includes(data.status))throw Error(`Invalid comment status: ${data.status}`);const commentData={title:data.title,body:data.body,status:data.status,commentables_id:data.commentables_id,commentables_type:data.commentables_type,user_id:data.user_id,is_active:data.is_active},written=await db.insertInto("commentables").values(commentData).returningAll().executeTakeFirst(),result=await resolveWrittenRow(db,"commentables",written);if(!result)throw Error("Failed to create comment");return result}catch(error){if(error instanceof Error)throw TypeError(`Failed to store comment: ${error.message}`);throw error}}
|
package/dist/database.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare function getDb(): Promise<StacksDatabase>;
|
|
2
|
-
declare type StacksDatabase = typeof import('@stacksjs/database').db;
|
|
2
|
+
declare type StacksDatabase = typeof import('@stacksjs/database/runtime').db;
|
package/dist/database.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let dbPromise;export async function getDb(){dbPromise??=import("@stacksjs/database").then((module)=>module.db);return dbPromise}
|
|
1
|
+
let dbPromise;export async function getDb(){dbPromise??=import("@stacksjs/database/runtime").then((module)=>module.db);return dbPromise}
|
package/dist/menus/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{asRows}from"@stacksjs/database";import{getDb}from"../database";export async function fetchMenuTree(siteId,handle){const db=await getDb(),menu=await db.selectFrom("menus").where("site_id","=",siteId).where("handle","=",handle).select(["id"]).executeTakeFirst();if(!menu)return[];const rows=asRows(await db.selectFrom("menu_items").leftJoin("pages","pages.id","=","menu_items.page_id").where("menu_items.menu_id","=",menu.id).select(["menu_items.id as id","menu_items.label as label","menu_items.url as url","menu_items.target as target","menu_items.parent_id as parent_id","menu_items.position as position","menu_items.visibility as visibility","pages.path as page_path","pages.status as page_status"]).orderBy("menu_items.position","asc").execute()),toItem=(row)=>{const href=row.url??(row.page_status==="published"?row.page_path:null);if(!href)return null;return{id:row.id,label:row.label,href,target:row.target==="_blank"?"_blank":"_self",visibility:row.visibility==="auth"?"auth":"public",children:[]}},byId=new Map,roots=[];for(const row of rows){const item=toItem(row);if(item)byId.set(row.id,item)}for(const row of rows){const item=byId.get(row.id);if(!item)continue;const parent=row.parent_id?byId.get(row.parent_id):void 0;if(parent)parent.children.push(item);else roots.push(item)}return roots}
|
|
1
|
+
import{asRows}from"@stacksjs/database/runtime";import{getDb}from"../database";export async function fetchMenuTree(siteId,handle){const db=await getDb(),menu=await db.selectFrom("menus").where("site_id","=",siteId).where("handle","=",handle).select(["id"]).executeTakeFirst();if(!menu)return[];const rows=asRows(await db.selectFrom("menu_items").leftJoin("pages","pages.id","=","menu_items.page_id").where("menu_items.menu_id","=",menu.id).select(["menu_items.id as id","menu_items.label as label","menu_items.url as url","menu_items.target as target","menu_items.parent_id as parent_id","menu_items.position as position","menu_items.visibility as visibility","pages.path as page_path","pages.status as page_status"]).orderBy("menu_items.position","asc").execute()),toItem=(row)=>{const href=row.url??(row.page_status==="published"?row.page_path:null);if(!href)return null;return{id:row.id,label:row.label,href,target:row.target==="_blank"?"_blank":"_self",visibility:row.visibility==="auth"?"auth":"public",children:[]}},byId=new Map,roots=[];for(const row of rows){const item=toItem(row);if(item)byId.set(row.id,item)}for(const row of rows){const item=byId.get(row.id);if(!item)continue;const parent=row.parent_id?byId.get(row.parent_id):void 0;if(parent)parent.children.push(item);else roots.push(item)}return roots}
|
package/dist/pages/document.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{formatDate}from"@stacksjs/orm";import{slugify}from"ts-slug";import{parseStoredBlocks,validateBlocks}from"../blocks/registry";import{getDb}from"../database";import{recordSlugChangeRedirects}from"../redirects";import{storeRevision}from"../revisions";class PageDocumentError extends Error{details;status=422;constructor(message,details){super(message);this.details=details;this.name="PageDocumentError"}}async function parentPath(db,siteId,parentId){if(!parentId)return"";const parent=await db.selectFrom("pages").where("id","=",parentId).where("site_id","=",siteId).select(["path"]).executeTakeFirst();if(!parent?.path)throw new PageDocumentError(`Parent page ${parentId} not found on this site`);return parent.path==="/"?"":parent.path}async function uniquePathFor(db,siteId,basePath,requestedSlug,excludePageId){const cleaned=slugify(requestedSlug);if(!cleaned)throw new PageDocumentError("Slug resolves to an empty string");let candidate=cleaned;for(let attempt=2;attempt<100;attempt++){const path=`${basePath}/${candidate}`;let query=db.selectFrom("pages").where("site_id","=",siteId).where("path","=",path).select(["id"]);if(excludePageId)query=query.where("id","!=",excludePageId);if(!await query.executeTakeFirst())return{slug:candidate,path};candidate=`${cleaned}-${attempt}`}throw new PageDocumentError(`Could not find a free path for "${cleaned}"`)}export async function createPageDocument(siteId,input){const db=await getDb(),validated=await validateBlocks(input.blocks??[]);if(!validated.ok)throw new PageDocumentError("Invalid page blocks",validated.errors);const isRoot=input.slug==="/",base=isRoot?"":await parentPath(db,siteId,input.parentId),{slug,path}=isRoot?{slug:"/",path:"/"}:await uniquePathFor(db,siteId,base,input.slug||input.title),now=formatDate(new Date),written=await db.insertInto("pages").values({site_id:siteId,author_id:input.authorId??null,parent_id:isRoot?null:input.parentId??null,title:input.title,slug,path,template:input.template??"default",blocks:JSON.stringify(validated.blocks),meta_description:input.metaDescription??null,status:input.status??"draft",scheduled_at:input.scheduledAt??null,published_at:input.status==="published"?now:null,views:0,conversions:0,created_at:now,updated_at:now}).returningAll().executeTakeFirst(),id=Number(written?.id);if(!id){const row=await db.selectFrom("pages").where("site_id","=",siteId).where("path","=",path).select(["id"]).executeTakeFirst();if(!row)throw Error("Failed to create page");return{id:Number(row.id),path,blocks:validated.blocks}}return{id,path,blocks:validated.blocks}}export async function updatePageDocument(siteId,pageId,input){const db=await getDb(),current=await db.selectFrom("pages").where("id","=",pageId).where("site_id","=",siteId).select(["id","site_id","parent_id","title","slug","path","blocks","meta_description"]).executeTakeFirst();if(!current)throw new PageDocumentError(`Page ${pageId} not found on this site`);const validated=await validateBlocks(input.blocks??[]);if(!validated.ok)throw new PageDocumentError("Invalid page blocks",validated.errors);await storeRevision(pageId,{title:current.title,blocks:current.blocks,metaDescription:current.meta_description},{authorId:input.authorId,note:input.note??null});const isRoot=(input.slug??current.slug)==="/",nextParentId=input.parentId!==void 0?input.parentId:current.parent_id,base=isRoot?"":await parentPath(db,siteId,nextParentId),requestedSlug=input.slug||current.slug||input.title,{slug,path}=isRoot?{slug:"/",path:"/"}:await uniquePathFor(db,siteId,base,requestedSlug,pageId),now=formatDate(new Date);await db.updateTable("pages").set({title:input.title,slug,path,parent_id:isRoot?null:nextParentId??null,...input.template!==void 0&&{template:input.template},blocks:JSON.stringify(validated.blocks),meta_description:input.metaDescription??null,...input.status!==void 0&&{status:input.status},...input.scheduledAt!==void 0&&{scheduled_at:input.scheduledAt},...input.status==="published"&&{published_at:now},updated_at:now}).where("id","=",pageId).execute();if(current.path&¤t.path!==path){const moves=[{fromPath:current.path,toPath:path}],descendants=await db.selectFrom("pages").where("site_id","=",siteId).where("path","like",`${current.path}/%`).select(["id","path"]).execute();for(const descendant of descendants){const nextPath=path+descendant.path.slice(current.path.length);await db.updateTable("pages").set({path:nextPath,updated_at:now}).where("id","=",descendant.id).execute();moves.push({fromPath:descendant.path,toPath:nextPath})}await recordSlugChangeRedirects(siteId,moves)}return{id:pageId,path,blocks:validated.blocks}}export{PageDocumentError};export async function fetchPageDocument(siteId,pageId){const row=await(await getDb()).selectFrom("pages").where("id","=",pageId).where("site_id","=",siteId).selectAll().executeTakeFirst();if(!row)return null;return{id:Number(row.id),siteId:Number(row.site_id),title:String(row.title??""),slug:String(row.slug??""),path:String(row.path??""),parentId:row.parent_id===null||row.parent_id===void 0?null:Number(row.parent_id),template:row.template===null||row.template===void 0?null:String(row.template),metaDescription:row.meta_description===null||row.meta_description===void 0?null:String(row.meta_description),status:String(row.status??"draft"),scheduledAt:
|
|
1
|
+
import{parseSqlDateTime,sqlDateTime}from"@stacksjs/database/runtime";import{formatDate}from"@stacksjs/orm";import{slugify}from"ts-slug";import{parseStoredBlocks,validateBlocks}from"../blocks/registry";import{getDb}from"../database";import{recordSlugChangeRedirects}from"../redirects";import{storeRevision}from"../revisions";class PageDocumentError extends Error{details;status=422;constructor(message,details){super(message);this.details=details;this.name="PageDocumentError"}}async function parentPath(db,siteId,parentId){if(!parentId)return"";const parent=await db.selectFrom("pages").where("id","=",parentId).where("site_id","=",siteId).select(["path"]).executeTakeFirst();if(!parent?.path)throw new PageDocumentError(`Parent page ${parentId} not found on this site`);return parent.path==="/"?"":parent.path}async function uniquePathFor(db,siteId,basePath,requestedSlug,excludePageId){const cleaned=slugify(requestedSlug);if(!cleaned)throw new PageDocumentError("Slug resolves to an empty string");let candidate=cleaned;for(let attempt=2;attempt<100;attempt++){const path=`${basePath}/${candidate}`;let query=db.selectFrom("pages").where("site_id","=",siteId).where("path","=",path).select(["id"]);if(excludePageId)query=query.where("id","!=",excludePageId);if(!await query.executeTakeFirst())return{slug:candidate,path};candidate=`${cleaned}-${attempt}`}throw new PageDocumentError(`Could not find a free path for "${cleaned}"`)}export async function createPageDocument(siteId,input){const db=await getDb(),validated=await validateBlocks(input.blocks??[]);if(!validated.ok)throw new PageDocumentError("Invalid page blocks",validated.errors);const isRoot=input.slug==="/",base=isRoot?"":await parentPath(db,siteId,input.parentId),{slug,path}=isRoot?{slug:"/",path:"/"}:await uniquePathFor(db,siteId,base,input.slug||input.title),now=formatDate(new Date),written=await db.insertInto("pages").values({site_id:siteId,author_id:input.authorId??null,parent_id:isRoot?null:input.parentId??null,title:input.title,slug,path,template:input.template??"default",blocks:JSON.stringify(validated.blocks),meta_description:input.metaDescription??null,status:input.status??"draft",scheduled_at:input.scheduledAt??null,published_at:input.status==="published"?now:null,views:0,conversions:0,created_at:now,updated_at:now}).returningAll().executeTakeFirst(),id=Number(written?.id);if(!id){const row=await db.selectFrom("pages").where("site_id","=",siteId).where("path","=",path).select(["id"]).executeTakeFirst();if(!row)throw Error("Failed to create page");return{id:Number(row.id),path,blocks:validated.blocks}}return{id,path,blocks:validated.blocks}}export async function updatePageDocument(siteId,pageId,input){const db=await getDb(),current=await db.selectFrom("pages").where("id","=",pageId).where("site_id","=",siteId).select(["id","site_id","parent_id","title","slug","path","blocks","meta_description"]).executeTakeFirst();if(!current)throw new PageDocumentError(`Page ${pageId} not found on this site`);const validated=await validateBlocks(input.blocks??[]);if(!validated.ok)throw new PageDocumentError("Invalid page blocks",validated.errors);await storeRevision(pageId,{title:current.title,blocks:current.blocks,metaDescription:current.meta_description},{authorId:input.authorId,note:input.note??null});const isRoot=(input.slug??current.slug)==="/",nextParentId=input.parentId!==void 0?input.parentId:current.parent_id,base=isRoot?"":await parentPath(db,siteId,nextParentId),requestedSlug=input.slug||current.slug||input.title,{slug,path}=isRoot?{slug:"/",path:"/"}:await uniquePathFor(db,siteId,base,requestedSlug,pageId),now=formatDate(new Date);await db.updateTable("pages").set({title:input.title,slug,path,parent_id:isRoot?null:nextParentId??null,...input.template!==void 0&&{template:input.template},blocks:JSON.stringify(validated.blocks),meta_description:input.metaDescription??null,...input.status!==void 0&&{status:input.status},...input.scheduledAt!==void 0&&{scheduled_at:input.scheduledAt},...input.status==="published"&&{published_at:now},updated_at:now}).where("id","=",pageId).execute();if(current.path&¤t.path!==path){const moves=[{fromPath:current.path,toPath:path}],descendants=await db.selectFrom("pages").where("site_id","=",siteId).where("path","like",`${current.path}/%`).select(["id","path"]).execute();for(const descendant of descendants){const nextPath=path+descendant.path.slice(current.path.length);await db.updateTable("pages").set({path:nextPath,updated_at:now}).where("id","=",descendant.id).execute();moves.push({fromPath:descendant.path,toPath:nextPath})}await recordSlugChangeRedirects(siteId,moves)}return{id:pageId,path,blocks:validated.blocks}}export{PageDocumentError};function storedTimestamp(value){if(value===null||value===void 0||value==="")return null;if(typeof value==="string")return value;const parsed=parseSqlDateTime(value);return parsed===null?null:sqlDateTime(parsed)}export async function fetchPageDocument(siteId,pageId){const row=await(await getDb()).selectFrom("pages").where("id","=",pageId).where("site_id","=",siteId).selectAll().executeTakeFirst();if(!row)return null;return{id:Number(row.id),siteId:Number(row.site_id),title:String(row.title??""),slug:String(row.slug??""),path:String(row.path??""),parentId:row.parent_id===null||row.parent_id===void 0?null:Number(row.parent_id),template:row.template===null||row.template===void 0?null:String(row.template),metaDescription:row.meta_description===null||row.meta_description===void 0?null:String(row.meta_description),status:String(row.status??"draft"),scheduledAt:storedTimestamp(row.scheduled_at),publishedAt:storedTimestamp(row.published_at),updatedAt:storedTimestamp(row.updated_at),blocks:parseStoredBlocks(row.blocks)}}
|
package/dist/pages/fetch.d.ts
CHANGED
package/dist/pages/update.d.ts
CHANGED
package/dist/taggables/fetch.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{sql}from"@stacksjs/database";import{getDb}from"../database";import{findOrCreate}from"./store";export async function fetchTagById(id){const db=await getDb();try{const result=await db.selectFrom("taggables").where("id","=",id).selectAll().executeTakeFirst();if(!result)return;return result}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch tag: ${error.message}`);throw error}}export async function fetchTags(){const db=await getDb();try{return await db.selectFrom("taggables").where("is_active","=",!0).selectAll().execute()}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch tags: ${error.message}`);throw error}}export async function firstOrCreate(name,taggableType,description){try{return await findOrCreate({name,taggable_type:taggableType,description})}catch(error){if(error instanceof Error)throw TypeError(`Failed to find or create tag: ${error.message}`);throw error}}export async function countTaggedPosts(taggableType){const db=await getDb();try{const result=await db.selectFrom("taggable_models").where("taggable_type","=",taggableType).count();return Number(result)||0}catch(error){if(error instanceof Error)throw TypeError(`Failed to count tagged posts: ${error.message}`);throw error}}export async function countTotalTags(){const db=await getDb();try{const result=await db.selectFrom("taggables").count();return Number(result)||0}catch(error){if(error instanceof Error)throw TypeError(`Failed to count total tags: ${error.message}`);throw error}}export async function findMostUsedTag(taggableType){const db=await getDb();try{let query=db.selectFrom("taggable_models").innerJoin("tags","tags.id","=","taggable_models.tag_id").select(["tags.name",sql`count(*)`.as("usage_count")]).groupBy("tags.name");if(taggableType)query=query.where("taggable_models.taggable_type","=",taggableType);const result=await query.orderBy("usage_count","desc").executeTakeFirst();if(!result)return null;return{name:result.name,count:Number(result.usage_count||0)}}catch(error){if(error instanceof Error)throw TypeError(`Failed to find most used tag: ${error.message}`);throw error}}export async function findLeastUsedTag(){const db=await getDb();try{const result=await db.selectFrom("taggable_models").innerJoin("tags","tags.id","=","taggable_models.tag_id").select(["tags.name",sql`count(*)`.as("usage_count")]).groupBy("tags.name").orderBy("usage_count","asc").executeTakeFirst();if(!result)return null;return{name:result.name,count:Number(result.usage_count||0)}}catch(error){if(error instanceof Error)throw TypeError(`Failed to find least used tag: ${error.message}`);throw error}}export async function fetchTagsWithPostCounts(){const db=await getDb();try{return(await db.selectFrom("tags").leftJoin("taggable_models","tags.id","=","taggable_models.tag_id").select(["tags.name",sql`count(taggable_models.id)`.as("post_count")]).groupBy("tags.name").orderBy("post_count","desc").limit(10).execute()).map((row)=>({name:row.name,postCount:Number(row.post_count||0)}))}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch tags with post counts: ${error.message}`);throw error}}export async function fetchTagDistribution(){const db=await getDb();try{const typedResult=await db.selectFrom("tags").leftJoin("taggable_models","tags.id","=","taggable_models.tag_id").select(["tags.name",sql`count(taggable_models.id)`.as("count")]).groupBy("tags.name").orderBy("count","desc").execute(),totalCount=typedResult.reduce((sum,row)=>sum+Number(row.count||0),0);return typedResult.map((row)=>({name:row.name,count:Number(row.count||0),percentage:totalCount>0?Number(row.count||0)/totalCount*100:0}))}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch tag distribution: ${error.message}`);throw error}}
|
|
1
|
+
import{sql}from"@stacksjs/database/runtime";import{getDb}from"../database";import{findOrCreate}from"./store";export async function fetchTagById(id){const db=await getDb();try{const result=await db.selectFrom("taggables").where("id","=",id).selectAll().executeTakeFirst();if(!result)return;return result}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch tag: ${error.message}`);throw error}}export async function fetchTags(){const db=await getDb();try{return await db.selectFrom("taggables").where("is_active","=",!0).selectAll().execute()}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch tags: ${error.message}`);throw error}}export async function firstOrCreate(name,taggableType,description){try{return await findOrCreate({name,taggable_type:taggableType,description})}catch(error){if(error instanceof Error)throw TypeError(`Failed to find or create tag: ${error.message}`);throw error}}export async function countTaggedPosts(taggableType){const db=await getDb();try{const result=await db.selectFrom("taggable_models").where("taggable_type","=",taggableType).count();return Number(result)||0}catch(error){if(error instanceof Error)throw TypeError(`Failed to count tagged posts: ${error.message}`);throw error}}export async function countTotalTags(){const db=await getDb();try{const result=await db.selectFrom("taggables").count();return Number(result)||0}catch(error){if(error instanceof Error)throw TypeError(`Failed to count total tags: ${error.message}`);throw error}}export async function findMostUsedTag(taggableType){const db=await getDb();try{let query=db.selectFrom("taggable_models").innerJoin("tags","tags.id","=","taggable_models.tag_id").select(["tags.name",sql`count(*)`.as("usage_count")]).groupBy("tags.name");if(taggableType)query=query.where("taggable_models.taggable_type","=",taggableType);const result=await query.orderBy("usage_count","desc").executeTakeFirst();if(!result)return null;return{name:result.name,count:Number(result.usage_count||0)}}catch(error){if(error instanceof Error)throw TypeError(`Failed to find most used tag: ${error.message}`);throw error}}export async function findLeastUsedTag(){const db=await getDb();try{const result=await db.selectFrom("taggable_models").innerJoin("tags","tags.id","=","taggable_models.tag_id").select(["tags.name",sql`count(*)`.as("usage_count")]).groupBy("tags.name").orderBy("usage_count","asc").executeTakeFirst();if(!result)return null;return{name:result.name,count:Number(result.usage_count||0)}}catch(error){if(error instanceof Error)throw TypeError(`Failed to find least used tag: ${error.message}`);throw error}}export async function fetchTagsWithPostCounts(){const db=await getDb();try{return(await db.selectFrom("tags").leftJoin("taggable_models","tags.id","=","taggable_models.tag_id").select(["tags.name",sql`count(taggable_models.id)`.as("post_count")]).groupBy("tags.name").orderBy("post_count","desc").limit(10).execute()).map((row)=>({name:row.name,postCount:Number(row.post_count||0)}))}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch tags with post counts: ${error.message}`);throw error}}export async function fetchTagDistribution(){const db=await getDb();try{const typedResult=await db.selectFrom("tags").leftJoin("taggable_models","tags.id","=","taggable_models.tag_id").select(["tags.name",sql`count(taggable_models.id)`.as("count")]).groupBy("tags.name").orderBy("count","desc").execute(),totalCount=typedResult.reduce((sum,row)=>sum+Number(row.count||0),0);return typedResult.map((row)=>({name:row.name,count:Number(row.count||0),percentage:totalCount>0?Number(row.count||0)/totalCount*100:0}))}catch(error){if(error instanceof Error)throw TypeError(`Failed to fetch tag distribution: ${error.message}`);throw error}}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/cms",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.48",
|
|
6
6
|
"description": "Stacks cms utilities.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"prepublishOnly": "bun run build"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@stacksjs/database": "0.74.
|
|
62
|
-
"@stacksjs/error-handling": "0.74.
|
|
63
|
-
"@stacksjs/orm": "0.74.
|
|
64
|
-
"@stacksjs/path": "0.74.
|
|
65
|
-
"@stacksjs/sites": "0.74.
|
|
66
|
-
"@stacksjs/slug": "0.74.
|
|
61
|
+
"@stacksjs/database": "0.74.48",
|
|
62
|
+
"@stacksjs/error-handling": "0.74.48",
|
|
63
|
+
"@stacksjs/orm": "0.74.48",
|
|
64
|
+
"@stacksjs/path": "0.74.48",
|
|
65
|
+
"@stacksjs/sites": "0.74.48",
|
|
66
|
+
"@stacksjs/slug": "0.74.48",
|
|
67
67
|
"@stacksjs/stx": "^0.2.286",
|
|
68
|
-
"@stacksjs/validation": "0.74.
|
|
68
|
+
"@stacksjs/validation": "0.74.48",
|
|
69
69
|
"ts-slug": "^0.1.0",
|
|
70
70
|
"ts-spreadsheets": "^0.2.0"
|
|
71
71
|
},
|