Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cms/sveltekit/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
PUBLIC_DIRECTUS_URL=http://localhost:8055
PUBLIC_DIRECTUS_TOKEN=STATIC_TOKEN_FROM_Webmaster_account
PUBLIC_SITE_URL=http://localhost:3000
PUBLIC_DIRECTUS_FORM_TOKEN=STATIC_TOKEN_FROM_Frontend_Bot_User_account
DRAFT_MODE_SECRET=your-draft-mode-secret
PUBLIC_ENABLE_VISUAL_EDITING=true
PUBLIC_ENABLE_VISUAL_EDITING=true

DIRECTUS_SERVER_TOKEN=STATIC_TOKEN_FROM_Webmaster_account
DRAFT_MODE_SECRET=your-draft-mode-secret
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import SearchModal from '../ui/SearchModal.svelte';
import LightSwitch from './LightSwitch.svelte';
import { PUBLIC_DIRECTUS_URL } from '$env/static/public';
import { goto } from '$app/navigation';
import { ChevronDown, Menu } from '@lucide/svelte';
import * as Collapsible from '$lib/components/ui/collapsible';
import setAttr from '$lib/directus/visualEditing';
Expand Down
22 changes: 18 additions & 4 deletions cms/sveltekit/src/lib/directus/directus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import type { RestClient } from '@directus/sdk';
import Queue from 'p-queue';
import type { Schema } from '../types/directus-schema';
import { PUBLIC_DIRECTUS_URL } from '$env/static/public';
import { getRequestEvent } from '$app/server';
import { browser } from '$app/environment';


// Helper for retrying fetch requests
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const fetchRetry = async (fetch: Function, count: number, ...args: any[]) => {
const response = await fetch(...args);
const fetchRetry = async (sveltekitFetch: typeof fetch, count: number, ...args: any[]) => {
const response = await sveltekitFetch(...args as Parameters<typeof fetch>);

if (count > 2 || response.status !== 429) return response;

Expand All @@ -33,7 +36,18 @@ const queue = new Queue({ intervalCap: 10, interval: 500, carryoverConcurrencyCo

const directusUrl = PUBLIC_DIRECTUS_URL;

const getDirectus = (fetch: Function) => {
const getDirectus = () => {

let fetch = globalThis.fetch;
if (!browser) {
// server side, so using sveltekit optimized fetch
// https://svelte.dev/docs/kit/load#Making-fetch-requests
// https://svelte.dev/docs/kit/$app-server#getRequestEvent
const { fetch: sveltekitFetch } = getRequestEvent();
fetch = sveltekitFetch;
} else {
// client side, so sticking with default fetch
}

const directus = createDirectus<Schema>(directusUrl, {
globals: {
Expand All @@ -46,7 +60,7 @@ const getDirectus = (fetch: Function) => {

export const useDirectus = () => ({
// directus: directus as RestClient<Schema>,
getDirectus: getDirectus as (fetch: Function) => RestClient<Schema>,
getDirectus: getDirectus as () => RestClient<Schema>,
readItems,
readItem,
readSingleton,
Expand Down
23 changes: 10 additions & 13 deletions cms/sveltekit/src/lib/directus/fetchers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { error } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';
import { type BlockPost, type PageBlock, type Post, type Schema } from '../types/directus-schema';
import { useDirectus } from './directus';
import { type QueryFilter, aggregate, readItem, readSingleton } from '@directus/sdk';
Expand All @@ -10,10 +9,9 @@ import { type QueryFilter, aggregate, readItem, readSingleton } from '@directus/
export const fetchPageData = async (
permalink: string,
postPage = 1,
fetch: RequestEvent['fetch']
) => {
const { getDirectus, readItems } = useDirectus();
const directus = getDirectus(fetch);
const directus = getDirectus();

const pageData = await directus.request(
readItems('pages', {
Expand Down Expand Up @@ -170,9 +168,9 @@ export const fetchPageData = async (
/**
* Fetches global site data, header navigation, and footer navigation.
*/
export const fetchSiteData = async (fetch: RequestEvent['fetch']) => {
export const fetchSiteData = async () => {
const { getDirectus } = useDirectus();
const directus = getDirectus(fetch);
const directus = getDirectus();

try {
const [globals, headerNavigation, footerNavigation] = await Promise.all([
Expand Down Expand Up @@ -242,10 +240,9 @@ export const fetchSiteData = async (fetch: RequestEvent['fetch']) => {
export const fetchPostBySlug = async (
slug: string,
options: { draft?: boolean },
fetch: RequestEvent['fetch']
) => {
const { getDirectus, readItems } = useDirectus();
const directus = getDirectus(fetch);
const directus = getDirectus();

try {
const filter: QueryFilter<Schema, Post> = options?.draft
Expand Down Expand Up @@ -278,9 +275,9 @@ export const fetchPostBySlug = async (
/**
* Fetches related blog posts excluding the given ID.
*/
export const fetchRelatedPosts = async (excludeId: string, fetch: RequestEvent['fetch']) => {
export const fetchRelatedPosts = async (excludeId: string) => {
const { getDirectus, readItems } = useDirectus();
const directus = getDirectus(fetch);
const directus = getDirectus();

try {
const relatedPosts = await directus.request(
Expand All @@ -301,9 +298,9 @@ export const fetchRelatedPosts = async (excludeId: string, fetch: RequestEvent['
/**
* Fetches author details by ID.
*/
export const fetchAuthorById = async (authorId: string, fetch: RequestEvent['fetch']) => {
export const fetchAuthorById = async (authorId: string) => {
const { getDirectus, readUser } = useDirectus();
const directus = getDirectus(fetch);
const directus = getDirectus();

try {
const author = await directus.request(
Expand All @@ -324,7 +321,7 @@ export const fetchAuthorById = async (authorId: string, fetch: RequestEvent['fet
*/
export const fetchPaginatedPosts = async (limit: number, page: number) => {
const { getDirectus, readItems } = useDirectus();
const directus = getDirectus(fetch);
const directus = getDirectus();
try {
const response = await directus.request(
readItems('posts', {
Expand All @@ -348,7 +345,7 @@ export const fetchPaginatedPosts = async (limit: number, page: number) => {
*/
export const fetchTotalPostCount = async (): Promise<number> => {
const { getDirectus } = useDirectus();
const directus = getDirectus(fetch);
const directus = getDirectus();

try {
const response = await directus.request(
Expand Down
12 changes: 4 additions & 8 deletions cms/sveltekit/src/lib/directus/forms.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { useDirectus } from './directus';
import { PUBLIC_DIRECTUS_FORM_TOKEN } from '$env/static/public';
interface SubmissionValue {
field: string;
value?: string;
file?: string;
}
import type { FormSubmission, FormSubmissionValue } from '$lib/types/directus-schema';

export const submitForm = async (
formId: string,
Expand All @@ -20,7 +16,7 @@ export const submitForm = async (
}

try {
const submissionValues: SubmissionValue[] = [];
const submissionValues: Array<Pick<FormSubmissionValue, 'field' | 'value' | 'file'>> = [];

for (const field of fields) {
const value = data[field.name];
Expand All @@ -47,9 +43,9 @@ export const submitForm = async (
}
}

const payload = {
const payload: Partial<FormSubmission> = {
form: formId,
values: submissionValues
values: submissionValues as unknown as FormSubmissionValue[]
};

await directus.request(withToken(TOKEN, createItem('form_submissions', payload)));
Expand Down
2 changes: 1 addition & 1 deletion cms/sveltekit/src/lib/directus/generateDirectusTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ config();

async function generateTypes() {
const directusUrl = process.env.PUBLIC_DIRECTUS_URL;
const directusToken = process.env.PUBLIC_DIRECTUS_TOKEN;
const directusToken = process.env.DIRECTUS_SERVER_TOKEN;

if (!directusUrl || !directusToken) {
console.error(
Expand Down
116 changes: 36 additions & 80 deletions cms/sveltekit/src/lib/types/directus-schema.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@

export interface ExtensionSeoMetadata {
title?: string;
meta_description?: string;
og_image?: string;
additional_fields?: Record<string, unknown>;
sitemap?: {
change_frequency: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
priority: string;
};
no_index?: boolean;
no_follow?: boolean;
title?: string;
meta_description?: string;
og_image?: string;
additional_fields?: Record<string, unknown>;
sitemap?: {
change_frequency: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
priority: string;
};
no_index?: boolean;
no_follow?: boolean;
}

export interface AiPrompt {
/** @primaryKey */
id: string;
sort?: number | null;
/** @description Unique name for the prompt. Use names like "create-article" or "generate-product-description". @required */
name: string;
/** @description Is this prompt published and available to use? */
status?: 'draft' | 'in_review' | 'published';
/** @description Briefly explain what this prompt does in 1-2 sentences. */
description?: string | null;
/** @description Instructions that shape how the AI responds. */
system_prompt?: string | null;
/** @description Optional: Define the conversation structure between users and AI. Used to add context and improve outputs. */
messages?: Array<{ role: 'user' | 'assistant'; text: string }> | null;
sort?: number | null;
/** @description Is this prompt published and available to use? */
status?: 'draft' | 'in_review' | 'published';
/** @description Instructions that shape how the AI responds. */
system_prompt?: string | null;
date_created?: string | null;
user_created?: DirectusUser | string | null;
date_updated?: string | null;
Expand Down Expand Up @@ -94,7 +95,7 @@ export interface BlockGallery {
date_updated?: string | null;
user_updated?: DirectusUser | string | null;
/** @description Images to include in the image gallery. */
items?: DirectusFile[] | string[] | null;
items?: BlockGalleryItem[] | string[];
}

export interface BlockGalleryItem {
Expand Down Expand Up @@ -212,16 +213,7 @@ export interface FormField {
/** @description Unique field identifier, not shown to users (lowercase, hyphenated) */
name?: string | null;
/** @description Input type for the field */
type?:
| 'text'
| 'textarea'
| 'checkbox'
| 'checkbox_group'
| 'radio'
| 'file'
| 'select'
| 'hidden'
| null;
type?: 'text' | 'textarea' | 'checkbox' | 'checkbox_group' | 'radio' | 'file' | 'select' | 'hidden' | null;
/** @description Text label shown to form users. */
label?: string | null;
/** @description Default text shown in empty input. */
Expand Down Expand Up @@ -304,19 +296,7 @@ export interface Globals {
/** @primaryKey */
id: string;
/** @description Social media profile URLs */
social_links?: Array<{
url: string;
service:
| 'facebook'
| 'instagram'
| 'linkedin'
| 'x'
| 'vimeo'
| 'youtube'
| 'github'
| 'discord'
| 'docker';
}> | null;
social_links?: Array<{ url: string; service: 'facebook' | 'instagram' | 'linkedin' | 'x' | 'vimeo' | 'youtube' | 'github' | 'discord' | 'docker' }> | null;
/** @description Short phrase describing the site. */
tagline?: string | null;
/** @description Main site title */
Expand All @@ -331,10 +311,10 @@ export interface Globals {
openai_api_key?: string | null;
/** @description The public URL for this Directus instance. Used in Flows. */
directus_url?: string | null;
/** @description Accent color for the website (used on buttons, links, etc). */
accent_color?: string | null;
/** @description Main logo shown on the site (for dark mode). */
logo_dark_mode?: DirectusFile | string | null;
/** @description Accent color for the website (used on buttons, links, etc). */
accent_color?: string | null;
date_created?: string | null;
user_created?: DirectusUser | string | null;
date_updated?: string | null;
Expand Down Expand Up @@ -389,15 +369,7 @@ export interface PageBlock {
/** @description The id of the page that this block belongs to. */
page?: Page | string | null;
/** @description The data for the block. */
item?:
| BlockHero
| BlockRichtext
| BlockForm
| BlockPost
| BlockGallery
| BlockPricing
| string
| null;
item?: BlockHero | BlockRichtext | BlockForm | BlockPost | BlockGallery | BlockPricing | string | null;
/** @description The collection (type of block). */
collection?: string | null;
/** @description Temporarily hide this block on the website without having to remove it from your page. */
Expand Down Expand Up @@ -505,12 +477,7 @@ export interface DirectusCollection {
display_template?: string | null;
hidden?: boolean;
singleton?: boolean;
translations?: Array<{
language: string;
translation: string;
singular: string;
plural: string;
}> | null;
translations?: Array<{ language: string; translation: string; singular: string; plural: string }> | null;
archive_field?: string | null;
archive_app_filter?: boolean;
archive_value?: string | null;
Expand Down Expand Up @@ -712,31 +679,12 @@ export interface DirectusSettings {
public_background?: DirectusFile | string | null;
public_note?: string | null;
auth_login_attempts?: number | null;
auth_password_policy?:
| null
| `/^.{8,}$/`
| `/(?=^.{8,}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{';'?>.<,])(?!.*\\s).*$/`
| null;
auth_password_policy?: null | `/^.{8,}$/` | `/(?=^.{8,}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{';'?>.<,])(?!.*\\s).*$/` | null;
storage_asset_transform?: 'all' | 'none' | 'presets' | null;
storage_asset_presets?: Array<{
key: string;
fit: 'contain' | 'cover' | 'inside' | 'outside';
width: number;
height: number;
quality: number;
withoutEnlargement: boolean;
format: 'auto' | 'jpeg' | 'png' | 'webp' | 'tiff' | 'avif';
transforms: 'json';
}> | null;
storage_asset_presets?: Array<{ key: string; fit: 'contain' | 'cover' | 'inside' | 'outside'; width: number; height: number; quality: number; withoutEnlargement: boolean; format: 'auto' | 'jpeg' | 'png' | 'webp' | 'tiff' | 'avif'; transforms: 'json' }> | null;
custom_css?: string | null;
storage_default_folder?: DirectusFolder | string | null;
basemaps?: Array<{
name: string;
type: 'raster' | 'tile' | 'style';
url: string;
tileSize: number;
attribution: string;
}> | null;
basemaps?: Array<{ name: string; type: 'raster' | 'tile' | 'style'; url: string; tileSize: number; attribution: string }> | null;
mapbox_key?: string | null;
module_bar?: 'json' | null;
project_descriptor?: string | null;
Expand All @@ -755,9 +703,16 @@ export interface DirectusSettings {
public_registration_verify_email?: boolean;
public_registration_role?: DirectusRole | string | null;
public_registration_email_filter?: 'json' | null;
visual_editor_urls?: Array<{ url: string }> | null;
accepted_terms?: boolean | null;
project_id?: string | null;
mcp_enabled?: boolean;
mcp_allow_deletes?: boolean;
mcp_prompts_collection?: string | null;
mcp_system_prompt_enabled?: boolean;
mcp_system_prompt?: string | null;
/** @description Settings for the Command Palette Module. */
command_palette_settings?: Record<string, any> | null;
visual_editor_urls?: Array<{ url: string }> | null;
}

export interface DirectusUser {
Expand Down Expand Up @@ -788,6 +743,7 @@ export interface DirectusUser {
theme_light?: string | null;
theme_light_overrides?: 'json' | null;
theme_dark_overrides?: 'json' | null;
text_direction?: 'auto' | 'ltr' | 'rtl';
/** @description Blog posts this user has authored. */
posts?: Post[] | string[];
policies?: DirectusAccess[] | string[];
Expand Down Expand Up @@ -1038,4 +994,4 @@ export enum CollectionNames {
directus_translations = 'directus_translations',
directus_versions = 'directus_versions',
directus_extensions = 'directus_extensions'
}
}
Loading