tina
This commit is contained in:
4
tina/.gitignore
vendored
Normal file
4
tina/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
__generated__
|
||||
node_modules
|
||||
.env
|
||||
*.log
|
||||
27
tina/auth/oidc-provider.ts
Normal file
27
tina/auth/oidc-provider.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { AbstractAuthProvider } from "tinacms";
|
||||
|
||||
export class OIDCAuthProvider extends AbstractAuthProvider {
|
||||
async authenticate(): Promise<any> {
|
||||
window.location.href = "/auth/login";
|
||||
}
|
||||
|
||||
async getUser(): Promise<any> {
|
||||
// Appel à une route /api/me pour récupérer l'utilisateur
|
||||
const res = await fetch("/api/me", {
|
||||
credentials: "include",
|
||||
});
|
||||
if (res.ok) {
|
||||
return await res.json();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getToken(): Promise<{ id_token: string }> {
|
||||
// Le token est géré par les cookies, pas besoin de le retourner
|
||||
return { id_token: "" };
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
window.location.href = "/auth/logout";
|
||||
}
|
||||
}
|
||||
617
tina/config.ts
Normal file
617
tina/config.ts
Normal file
@@ -0,0 +1,617 @@
|
||||
import { defineConfig, LocalAuthProvider } from "tinacms";
|
||||
import { OIDCAuthProvider } from "./auth/oidc-provider";
|
||||
|
||||
const isLocal = process.env.TINA_PUBLIC_IS_LOCAL === "true";
|
||||
|
||||
export default defineConfig({
|
||||
branch: "main",
|
||||
// Self-hosted: pas de clientId/token
|
||||
contentApiUrlOverride: isLocal
|
||||
? undefined
|
||||
: "https://tina.hec-ia.com/api/tina/gql",
|
||||
|
||||
authProvider: isLocal
|
||||
? new LocalAuthProvider()
|
||||
: new OIDCAuthProvider(),
|
||||
|
||||
build: {
|
||||
outputFolder: "admin",
|
||||
publicFolder: "public",
|
||||
},
|
||||
media: {
|
||||
tina: {
|
||||
mediaRoot: "uploads",
|
||||
publicFolder: "public",
|
||||
},
|
||||
},
|
||||
search: {
|
||||
tina: {
|
||||
indexerToken: "", // Mode local
|
||||
stopwordLanguages: ["fra"],
|
||||
},
|
||||
},
|
||||
schema: {
|
||||
collections: [
|
||||
{
|
||||
name: "blog",
|
||||
label: "Articles de blog",
|
||||
path: "src/data/blog",
|
||||
format: "md",
|
||||
ui: {
|
||||
filename: {
|
||||
readonly: false,
|
||||
slugify: (values) => {
|
||||
return values?.title
|
||||
?.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/\s+/g, "-");
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "title",
|
||||
label: "Titre",
|
||||
isTitle: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "description",
|
||||
label: "Description",
|
||||
required: true,
|
||||
ui: {
|
||||
component: "textarea",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "author",
|
||||
label: "Auteur",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "pubDatetime",
|
||||
label: "Date de publication",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "modDatetime",
|
||||
label: "Date de modification",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "tags",
|
||||
label: "Tags",
|
||||
list: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "draft",
|
||||
label: "Brouillon",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "featured",
|
||||
label: "Mis en avant",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "canonicalURL",
|
||||
label: "URL canonique",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "hideEditPost",
|
||||
label: "Cacher le lien d'édition",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "timezone",
|
||||
label: "Fuseau horaire",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "image" as const,
|
||||
name: "ogImage",
|
||||
label: "Image Open Graph",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "rich-text" as const,
|
||||
name: "body",
|
||||
label: "Contenu",
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "events",
|
||||
label: "Événements",
|
||||
path: "src/data/events",
|
||||
format: "md",
|
||||
ui: {
|
||||
filename: {
|
||||
readonly: false,
|
||||
slugify: (values) => {
|
||||
return values?.title
|
||||
?.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/\s+/g, "-");
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "title",
|
||||
label: "Titre",
|
||||
isTitle: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "description",
|
||||
label: "Description",
|
||||
required: true,
|
||||
ui: {
|
||||
component: "textarea",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "author",
|
||||
label: "Auteur",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "pubDatetime",
|
||||
label: "Date de publication",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "modDatetime",
|
||||
label: "Date de modification",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "tags",
|
||||
label: "Tags",
|
||||
list: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "draft",
|
||||
label: "Brouillon",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "featured",
|
||||
label: "Mis en avant",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "canonicalURL",
|
||||
label: "URL canonique",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "hideEditPost",
|
||||
label: "Cacher le lien d'édition",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "timezone",
|
||||
label: "Fuseau horaire",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "eventDate",
|
||||
label: "Date de l'événement",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "eventEndDate",
|
||||
label: "Date de fin",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "location",
|
||||
label: "Lieu",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "registrationLink",
|
||||
label: "Lien d'inscription",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "image" as const,
|
||||
name: "ogImage",
|
||||
label: "Image Open Graph",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "rich-text" as const,
|
||||
name: "body",
|
||||
label: "Contenu",
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "workshops",
|
||||
label: "Ateliers",
|
||||
path: "src/data/workshops",
|
||||
format: "md",
|
||||
ui: {
|
||||
filename: {
|
||||
readonly: false,
|
||||
slugify: (values) => {
|
||||
return values?.title
|
||||
?.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/\s+/g, "-");
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "title",
|
||||
label: "Titre",
|
||||
isTitle: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "description",
|
||||
label: "Description",
|
||||
required: true,
|
||||
ui: {
|
||||
component: "textarea",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "author",
|
||||
label: "Auteur",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "pubDatetime",
|
||||
label: "Date de publication",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "modDatetime",
|
||||
label: "Date de modification",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "tags",
|
||||
label: "Tags",
|
||||
list: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "draft",
|
||||
label: "Brouillon",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "featured",
|
||||
label: "Mis en avant",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "canonicalURL",
|
||||
label: "URL canonique",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "hideEditPost",
|
||||
label: "Cacher le lien d'édition",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "timezone",
|
||||
label: "Fuseau horaire",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "workshopDate",
|
||||
label: "Date de l'atelier",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "duration",
|
||||
label: "Durée",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "level",
|
||||
label: "Niveau",
|
||||
options: [
|
||||
{ label: "Débutant", value: "beginner" },
|
||||
{ label: "Intermédiaire", value: "intermediate" },
|
||||
{ label: "Avancé", value: "advanced" },
|
||||
],
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "materials",
|
||||
label: "Matériaux/Supports",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "image" as const,
|
||||
name: "ogImage",
|
||||
label: "Image Open Graph",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "rich-text" as const,
|
||||
name: "body",
|
||||
label: "Contenu",
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "news",
|
||||
label: "Actualités",
|
||||
path: "src/data/news",
|
||||
format: "md",
|
||||
ui: {
|
||||
filename: {
|
||||
readonly: false,
|
||||
slugify: (values) => {
|
||||
return values?.title
|
||||
?.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/\s+/g, "-");
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "title",
|
||||
label: "Titre",
|
||||
isTitle: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "description",
|
||||
label: "Description",
|
||||
required: true,
|
||||
ui: {
|
||||
component: "textarea",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "author",
|
||||
label: "Auteur",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "pubDatetime",
|
||||
label: "Date de publication",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "modDatetime",
|
||||
label: "Date de modification",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "tags",
|
||||
label: "Tags",
|
||||
list: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "draft",
|
||||
label: "Brouillon",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "featured",
|
||||
label: "Mis en avant",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "canonicalURL",
|
||||
label: "URL canonique",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "hideEditPost",
|
||||
label: "Cacher le lien d'édition",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "timezone",
|
||||
label: "Fuseau horaire",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "image" as const,
|
||||
name: "ogImage",
|
||||
label: "Image Open Graph",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "rich-text" as const,
|
||||
name: "body",
|
||||
label: "Contenu",
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "technical",
|
||||
label: "Articles techniques",
|
||||
path: "src/data/technical",
|
||||
format: "md",
|
||||
ui: {
|
||||
filename: {
|
||||
readonly: false,
|
||||
slugify: (values) => {
|
||||
return values?.title
|
||||
?.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/\s+/g, "-");
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "title",
|
||||
label: "Titre",
|
||||
isTitle: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "description",
|
||||
label: "Description",
|
||||
required: true,
|
||||
ui: {
|
||||
component: "textarea",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "author",
|
||||
label: "Auteur",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "pubDatetime",
|
||||
label: "Date de publication",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "datetime" as const,
|
||||
name: "modDatetime",
|
||||
label: "Date de modification",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "tags",
|
||||
label: "Tags",
|
||||
list: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "draft",
|
||||
label: "Brouillon",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "featured",
|
||||
label: "Mis en avant",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "canonicalURL",
|
||||
label: "URL canonique",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "boolean" as const,
|
||||
name: "hideEditPost",
|
||||
label: "Cacher le lien d'édition",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "timezone",
|
||||
label: "Fuseau horaire",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "difficulty",
|
||||
label: "Difficulté",
|
||||
options: [
|
||||
{ label: "Débutant", value: "beginner" },
|
||||
{ label: "Intermédiaire", value: "intermediate" },
|
||||
{ label: "Avancé", value: "advanced" },
|
||||
],
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "string" as const,
|
||||
name: "readingTime",
|
||||
label: "Temps de lecture",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "image" as const,
|
||||
name: "ogImage",
|
||||
label: "Image Open Graph",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: "rich-text" as const,
|
||||
name: "body",
|
||||
label: "Contenu",
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
1
tina/tina-lock.json
Normal file
1
tina/tina-lock.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user