Compare commits
5 commits
4e32b523d8
...
aeed521806
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeed521806 | ||
|
|
d18f291872 | ||
|
|
21590cd101 | ||
|
|
66bb076485 | ||
|
|
27a66bf320 |
17 changed files with 27761 additions and 20 deletions
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "drei-koenige-v3",
|
"name": "@bennotielen/parish-website",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "drei-koenige-v3",
|
"name": "@bennotielen/parish-website",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nyariv/sandboxjs": "0.8.28",
|
"@nyariv/sandboxjs": "0.8.28",
|
||||||
"@payloadcms/db-postgres": "^3.74.0",
|
"@payloadcms/db-postgres": "^3.74.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { CollectionConfig } from 'payload'
|
import { CollectionConfig } from 'payload'
|
||||||
import { hide, isAdminOrEmployee } from '@/collections/access/admin'
|
import { hide } from '@/collections/access/admin'
|
||||||
import { nextSunday } from '@/utils/sunday'
|
import { nextSunday } from '@/utils/sunday'
|
||||||
|
import { canCreateAssignedWorship, canMutateAssignedWorship } from '@/collections/access/assigned'
|
||||||
|
|
||||||
export const Announcements: CollectionConfig = {
|
export const Announcements: CollectionConfig = {
|
||||||
slug: 'announcement',
|
slug: 'announcement',
|
||||||
|
|
@ -53,8 +54,8 @@ export const Announcements: CollectionConfig = {
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
read: () => true,
|
read: () => true,
|
||||||
create: isAdminOrEmployee(),
|
create: canCreateAssignedWorship(),
|
||||||
update: isAdminOrEmployee(),
|
update: canMutateAssignedWorship(),
|
||||||
delete: isAdminOrEmployee(),
|
delete: canMutateAssignedWorship(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { CollectionConfig } from 'payload'
|
import { CollectionConfig } from 'payload'
|
||||||
import { isAdminOrEmployee } from '@/collections/access/admin'
|
import { isAdminOrEmployee } from '@/collections/access/admin'
|
||||||
import { canMutateOccurrenceOfAssignedEvent } from '@/collections/access/assigned'
|
import { canMutateOccurrenceOfAssignedEvent } from '@/collections/access/assigned'
|
||||||
|
import { revalidateTagHook } from '@/utils/revalidate'
|
||||||
|
|
||||||
export const EventOccurrences: CollectionConfig = {
|
export const EventOccurrences: CollectionConfig = {
|
||||||
slug: 'eventOccurrence',
|
slug: 'eventOccurrence',
|
||||||
|
|
@ -91,4 +92,8 @@ export const EventOccurrences: CollectionConfig = {
|
||||||
update: canMutateOccurrenceOfAssignedEvent(),
|
update: canMutateOccurrenceOfAssignedEvent(),
|
||||||
delete: isAdminOrEmployee(),
|
delete: isAdminOrEmployee(),
|
||||||
},
|
},
|
||||||
|
hooks: {
|
||||||
|
afterChange: [revalidateTagHook('events')],
|
||||||
|
afterDelete: [revalidateTagHook('events')],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
unassignedAdditions,
|
unassignedAdditions,
|
||||||
} from '@/collections/access/assigned'
|
} from '@/collections/access/assigned'
|
||||||
import { regenerateOccurrencesForEvent } from '@/jobs/generateEventOccurrences'
|
import { regenerateOccurrencesForEvent } from '@/jobs/generateEventOccurrences'
|
||||||
|
import { revalidateTagHook } from '@/utils/revalidate'
|
||||||
|
|
||||||
export const Events: CollectionConfig = {
|
export const Events: CollectionConfig = {
|
||||||
slug: 'event',
|
slug: 'event',
|
||||||
|
|
@ -431,6 +432,8 @@ export const Events: CollectionConfig = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
revalidateTagHook('events'),
|
||||||
],
|
],
|
||||||
beforeDelete: [
|
beforeDelete: [
|
||||||
async ({ id, req }) => {
|
async ({ id, req }) => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { CollectionConfig } from 'payload'
|
import { CollectionConfig } from 'payload'
|
||||||
|
import { revalidateTagHook } from '@/utils/revalidate'
|
||||||
import {
|
import {
|
||||||
canCreateAssignedWorship,
|
canCreateAssignedWorship,
|
||||||
canMutateAssignedWorship,
|
canMutateAssignedWorship,
|
||||||
|
|
@ -151,4 +152,8 @@ export const Worship: CollectionConfig = {
|
||||||
update: canMutateAssignedWorship(),
|
update: canMutateAssignedWorship(),
|
||||||
delete: canMutateAssignedWorship(),
|
delete: canMutateAssignedWorship(),
|
||||||
},
|
},
|
||||||
|
hooks: {
|
||||||
|
afterChange: [revalidateTagHook('worship')],
|
||||||
|
afterDelete: [revalidateTagHook('worship')],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,28 @@ export const MassTimesBlock: Block = {
|
||||||
de: 'Untertitel',
|
de: 'Untertitel',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'range',
|
||||||
|
type: 'select',
|
||||||
|
label: {
|
||||||
|
de: 'Zeitraum',
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: {
|
||||||
|
de: 'Ab heute (7 Tage)',
|
||||||
|
},
|
||||||
|
value: 'rolling',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: {
|
||||||
|
de: 'Aktuelle Woche (Montag bis Sonntag)',
|
||||||
|
},
|
||||||
|
value: 'week',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: 'rolling',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'churches',
|
name: 'churches',
|
||||||
label: {
|
label: {
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,7 @@ export function Blocks({ content }: BlocksProps) {
|
||||||
key={item.id}
|
key={item.id}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
subtitle={item.subtitle}
|
subtitle={item.subtitle}
|
||||||
|
range={item.range}
|
||||||
churches={item.churches}
|
churches={item.churches}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import styles from './massTimesBlock.module.scss'
|
||||||
type MassTimesBlockProps = {
|
type MassTimesBlockProps = {
|
||||||
title?: string | null
|
title?: string | null
|
||||||
subtitle?: string | null
|
subtitle?: string | null
|
||||||
|
range?: 'rolling' | 'week' | null
|
||||||
churches?: (string | Church)[] | null
|
churches?: (string | Church)[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,10 +41,15 @@ const sortWorship = (worship: Worship[]) => {
|
||||||
export async function MassTimesBlock({
|
export async function MassTimesBlock({
|
||||||
title = 'Nächste Gottesdienste',
|
title = 'Nächste Gottesdienste',
|
||||||
subtitle,
|
subtitle,
|
||||||
|
range,
|
||||||
churches,
|
churches,
|
||||||
}: MassTimesBlockProps) {
|
}: MassTimesBlockProps) {
|
||||||
const fromDate = moment().isoWeekday(1).hours(0).minutes(0)
|
const fromDate =
|
||||||
const tillDate = moment().isoWeekday(7).hours(23).minutes(59)
|
range === 'week' ? moment().isoWeekday(1).startOf('day') : moment()
|
||||||
|
const tillDate =
|
||||||
|
range === 'week'
|
||||||
|
? moment().isoWeekday(7).endOf('day')
|
||||||
|
: moment().add(1, 'week')
|
||||||
|
|
||||||
const churchIds = churches
|
const churchIds = churches
|
||||||
?.map((c) => (typeof c === 'object' ? c.id : c))
|
?.map((c) => (typeof c === 'object' ? c.id : c))
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ const getUpcomingOccurrences = unstable_cache(
|
||||||
}) as Promise<PaginatedDocs<EventOccurrence>>
|
}) as Promise<PaginatedDocs<EventOccurrence>>
|
||||||
},
|
},
|
||||||
['fetchUpcomingOccurrences'],
|
['fetchUpcomingOccurrences'],
|
||||||
{ revalidate: CACHE_TTL },
|
{ tags: ['events'], revalidate: CACHE_TTL },
|
||||||
)
|
)
|
||||||
|
|
||||||
export async function fetchUpcomingOccurrences(
|
export async function fetchUpcomingOccurrences(
|
||||||
|
|
@ -146,7 +146,7 @@ const getPastOccurrences = unstable_cache(
|
||||||
}) as Promise<PaginatedDocs<EventOccurrence>>
|
}) as Promise<PaginatedDocs<EventOccurrence>>
|
||||||
},
|
},
|
||||||
['fetchPastOccurrences'],
|
['fetchPastOccurrences'],
|
||||||
{ revalidate: CACHE_TTL },
|
{ tags: ['events'], revalidate: CACHE_TTL },
|
||||||
)
|
)
|
||||||
|
|
||||||
export async function fetchPastOccurrences(
|
export async function fetchPastOccurrences(
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ const getWorship = unstable_cache(
|
||||||
}) as Promise<PaginatedDocs<Worship>>
|
}) as Promise<PaginatedDocs<Worship>>
|
||||||
},
|
},
|
||||||
['fetchWorship'],
|
['fetchWorship'],
|
||||||
{ revalidate: CACHE_TTL },
|
{ tags: ['worship'], revalidate: CACHE_TTL },
|
||||||
)
|
)
|
||||||
|
|
||||||
export const fetchWorship = async (
|
export const fetchWorship = async (
|
||||||
|
|
|
||||||
27657
src/migrations/20260721_090435_add_mass_times_range.json
Normal file
27657
src/migrations/20260721_090435_add_mass_times_range.json
Normal file
File diff suppressed because it is too large
Load diff
23
src/migrations/20260721_090435_add_mass_times_range.ts
Normal file
23
src/migrations/20260721_090435_add_mass_times_range.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||||
|
|
||||||
|
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TYPE "public"."enum_pages_blocks_mass_times_range" AS ENUM('rolling', 'week');
|
||||||
|
CREATE TYPE "public"."enum__pages_v_blocks_mass_times_range" AS ENUM('rolling', 'week');
|
||||||
|
ALTER TABLE "announcement" ALTER COLUMN "date" SET DEFAULT '2026-07-26T09:04:34.613Z';
|
||||||
|
ALTER TABLE "calendar" ALTER COLUMN "date" SET DEFAULT '2026-07-26T09:04:34.950Z';
|
||||||
|
ALTER TABLE "classifieds" ALTER COLUMN "until" SET DEFAULT '2026-08-20T09:04:35.030Z';
|
||||||
|
ALTER TABLE "pages_blocks_mass_times" ADD COLUMN "range" "enum_pages_blocks_mass_times_range" DEFAULT 'rolling';
|
||||||
|
ALTER TABLE "_pages_v_blocks_mass_times" ADD COLUMN "range" "enum__pages_v_blocks_mass_times_range" DEFAULT 'rolling';`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
ALTER TABLE "announcement" ALTER COLUMN "date" SET DEFAULT '2026-07-19T11:32:04.857Z';
|
||||||
|
ALTER TABLE "calendar" ALTER COLUMN "date" SET DEFAULT '2026-07-19T11:32:05.134Z';
|
||||||
|
ALTER TABLE "classifieds" ALTER COLUMN "until" SET DEFAULT '2026-08-15T11:32:05.193Z';
|
||||||
|
ALTER TABLE "pages_blocks_mass_times" DROP COLUMN "range";
|
||||||
|
ALTER TABLE "_pages_v_blocks_mass_times" DROP COLUMN "range";
|
||||||
|
DROP TYPE "public"."enum_pages_blocks_mass_times_range";
|
||||||
|
DROP TYPE "public"."enum__pages_v_blocks_mass_times_range";`)
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,7 @@ import * as migration_20260611_072650_add_contact_person_to_group from './202606
|
||||||
import * as migration_20260611_084920_add_image_with_text_block from './20260611_084920_add_image_with_text_block';
|
import * as migration_20260611_084920_add_image_with_text_block from './20260611_084920_add_image_with_text_block';
|
||||||
import * as migration_20260716_090032_add_user_parish_page_assignments from './20260716_090032_add_user_parish_page_assignments';
|
import * as migration_20260716_090032_add_user_parish_page_assignments from './20260716_090032_add_user_parish_page_assignments';
|
||||||
import * as migration_20260716_113205_add_blog_pinned from './20260716_113205_add_blog_pinned';
|
import * as migration_20260716_113205_add_blog_pinned from './20260716_113205_add_blog_pinned';
|
||||||
|
import * as migration_20260721_090435_add_mass_times_range from './20260721_090435_add_mass_times_range';
|
||||||
|
|
||||||
export const migrations = [
|
export const migrations = [
|
||||||
{
|
{
|
||||||
|
|
@ -328,6 +329,11 @@ export const migrations = [
|
||||||
{
|
{
|
||||||
up: migration_20260716_113205_add_blog_pinned.up,
|
up: migration_20260716_113205_add_blog_pinned.up,
|
||||||
down: migration_20260716_113205_add_blog_pinned.down,
|
down: migration_20260716_113205_add_blog_pinned.down,
|
||||||
name: '20260716_113205_add_blog_pinned'
|
name: '20260716_113205_add_blog_pinned',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
up: migration_20260721_090435_add_mass_times_range.up,
|
||||||
|
down: migration_20260721_090435_add_mass_times_range.down,
|
||||||
|
name: '20260721_090435_add_mass_times_range'
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -712,6 +712,7 @@ export interface Page {
|
||||||
| {
|
| {
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
subtitle?: string | null;
|
subtitle?: string | null;
|
||||||
|
range?: ('rolling' | 'week') | null;
|
||||||
/**
|
/**
|
||||||
* Leer lassen, um alle Kirchen alphabetisch anzuzeigen.
|
* Leer lassen, um alle Kirchen alphabetisch anzuzeigen.
|
||||||
*/
|
*/
|
||||||
|
|
@ -2429,6 +2430,7 @@ export interface PagesSelect<T extends boolean = true> {
|
||||||
| {
|
| {
|
||||||
title?: T;
|
title?: T;
|
||||||
subtitle?: T;
|
subtitle?: T;
|
||||||
|
range?: T;
|
||||||
churches?: T;
|
churches?: T;
|
||||||
id?: T;
|
id?: T;
|
||||||
blockName?: T;
|
blockName?: T;
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,6 @@ describe('isSpam function', () => {
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not classify a German message with a URL as spam', () => {
|
|
||||||
const spamMessage = "Schauen Sie sich diese Website für tolle Angebote an https://www.spamsite.com";
|
|
||||||
const result = isSpam(spamMessage);
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should classify one word message as spam", () => {
|
it("should classify one word message as spam", () => {
|
||||||
const spamMessage = 'yolo';
|
const spamMessage = 'yolo';
|
||||||
const result = isSpam(spamMessage);
|
const result = isSpam(spamMessage);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export const eventToPageProps = (
|
||||||
id: event.id,
|
id: event.id,
|
||||||
title: event.title,
|
title: event.title,
|
||||||
date: occurrence?.date ?? event.date,
|
date: occurrence?.date ?? event.date,
|
||||||
endDateTime: event.endDateTime ?? undefined,
|
endDateTime: occurrence?.endDateTime ?? event.endDateTime ?? undefined,
|
||||||
createdAt: event.createdAt,
|
createdAt: event.createdAt,
|
||||||
cancelled: Boolean(event.cancelled || occurrence?.cancelled),
|
cancelled: Boolean(event.cancelled || occurrence?.cancelled),
|
||||||
recurrenceType: event.recurrenceType,
|
recurrenceType: event.recurrenceType,
|
||||||
|
|
|
||||||
16
src/utils/revalidate.ts
Normal file
16
src/utils/revalidate.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { revalidateTag } from 'next/cache'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Payload hook that invalidates a Next cache tag.
|
||||||
|
*
|
||||||
|
* Safe to use for docs created/deleted programmatically (e.g. by
|
||||||
|
* regenerateMassesForChurch / regenerateOccurrencesForEvent), since the
|
||||||
|
* Local API runs collection hooks. The jobs queue can execute outside a
|
||||||
|
* Next request scope where revalidateTag throws — swallow that; the
|
||||||
|
* fetch-side TTL covers the gap there.
|
||||||
|
*/
|
||||||
|
export const revalidateTagHook = (tag: string) => () => {
|
||||||
|
try {
|
||||||
|
revalidateTag(tag)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue