1
1
import { Metadata } from 'next' ;
2
+ import { headers } from 'next/headers' ;
3
+ import { notFound } from 'next/navigation' ;
4
+ import { userAgent } from 'next/server' ;
5
+ import { CreativeWork , WithContext } from 'schema-dts' ;
2
6
3
7
import { InitiativePage , InitiativePageProps } from '@etabli/src/app/(public)/initiative/[initiativeId]/InitiativePage' ;
8
+ import { useServerTranslation } from '@etabli/src/i18n' ;
4
9
import { GetInitiativeSchema } from '@etabli/src/models/actions/initiative' ;
5
10
import { prisma } from '@etabli/src/prisma' ;
11
+ import { initiativePrismaToModel } from '@etabli/src/server/routers/mappers' ;
6
12
import { formatPageTitle } from '@etabli/src/utils/page' ;
7
13
8
14
export async function generateMetadata ( props : InitiativePageProps ) : Promise < Metadata > {
@@ -27,6 +33,98 @@ export async function generateMetadata(props: InitiativePageProps): Promise<Meta
27
33
} ;
28
34
}
29
35
30
- export default function Page ( props : InitiativePageProps ) {
31
- return < InitiativePage { ...props } /> ;
36
+ export default async function Page ( props : InitiativePageProps ) {
37
+ const { t } = useServerTranslation ( 'common' ) ;
38
+
39
+ const userAgentObject = userAgent ( { headers : headers ( ) } ) ;
40
+
41
+ // Since this page has a dynamic pathname and the data is fetched from the frontend we add a condition so search engines
42
+ // still have content to index in case they don't do client rendering with `puppeteer` or equivalent
43
+ let initiativeJsonLd : WithContext < CreativeWork > | null = null ;
44
+ if ( userAgentObject . isBot ) {
45
+ const result = GetInitiativeSchema . shape . id . safeParse ( props . params . initiativeId ) ;
46
+ if ( ! result . success ) {
47
+ return notFound ( ) ;
48
+ }
49
+
50
+ // We rely on the UUID validation from `generateMetadata()`
51
+ const dbInitiative = await prisma . initiative . findUnique ( {
52
+ where : {
53
+ id : result . data ,
54
+ } ,
55
+ include : {
56
+ ToolsOnInitiatives : {
57
+ include : {
58
+ tool : {
59
+ select : {
60
+ name : true ,
61
+ } ,
62
+ } ,
63
+ } ,
64
+ } ,
65
+ BusinessUseCasesOnInitiatives : {
66
+ include : {
67
+ businessUseCase : {
68
+ select : {
69
+ name : true ,
70
+ } ,
71
+ } ,
72
+ } ,
73
+ } ,
74
+ } ,
75
+ } ) ;
76
+
77
+ if ( ! dbInitiative ) {
78
+ return notFound ( ) ;
79
+ }
80
+
81
+ const initiative = initiativePrismaToModel ( {
82
+ ...dbInitiative ,
83
+ businessUseCases : dbInitiative . BusinessUseCasesOnInitiatives . map ( ( bucOnI ) => bucOnI . businessUseCase . name ) ,
84
+ tools : dbInitiative . ToolsOnInitiatives . map ( ( toolOnI ) => toolOnI . tool . name ) ,
85
+ } ) ;
86
+
87
+ initiativeJsonLd = {
88
+ '@context' : 'https://schema.org' ,
89
+ '@type' : 'CreativeWork' ,
90
+ // TODO: the link registry cannot be used server-side so not specifying the id (https://github.com/zilch/type-route/issues/125)
91
+ // '@id': linkRegistry.get('initiative', { initiativeId: initiative.id }, { absolute: true }), // Must be unique as IRI
92
+ name : initiative . name ,
93
+ description : initiative . description ,
94
+ url : initiative . websites ,
95
+ about : [
96
+ {
97
+ '@type' : 'SoftwareSourceCode' ,
98
+ codeRepository : initiative . repositories ,
99
+ keywords : initiative . tools ,
100
+ } ,
101
+ ] ,
102
+ potentialAction : [
103
+ ...initiative . businessUseCases . map ( ( businessUseCase ) => {
104
+ return {
105
+ '@type' : 'Action' as 'Action' ,
106
+ description : businessUseCase ,
107
+ disambiguatingDescription : `${ t ( 'model.initiative.businessUseCase' , { count : 1 } ) } : ${ businessUseCase } ` ,
108
+ } ;
109
+ } ) ,
110
+ ...initiative . functionalUseCases . map ( ( functionalUseCase ) => {
111
+ const functionalUseCaseName = t ( `model.initiative.functionalUseCase.enum.${ functionalUseCase } ` ) ;
112
+
113
+ return {
114
+ '@type' : 'Action' as 'Action' ,
115
+ description : functionalUseCaseName ,
116
+ disambiguatingDescription : `${ t ( 'model.initiative.functionalUseCase.label' , { count : 1 } ) } : ${ functionalUseCaseName } ` ,
117
+ } ;
118
+ } ) ,
119
+ ] ,
120
+ dateModified : initiative . updatedAt . toISOString ( ) ,
121
+ } ;
122
+ }
123
+
124
+ return (
125
+ < >
126
+ { ! ! initiativeJsonLd && < script type = "application/ld+json" dangerouslySetInnerHTML = { { __html : JSON . stringify ( initiativeJsonLd ) } } /> }
127
+ < InitiativePage { ...props } />
128
+ </ >
129
+ ) ;
32
130
}
0 commit comments