diff --git a/.env.production.example b/.env.production.example index 6c2e178..483f8bc 100644 --- a/.env.production.example +++ b/.env.production.example @@ -5,11 +5,7 @@ NEXTAUTH_SECRET=change-me-to-a-random-secret NEXTAUTH_URL=https://docs.linumiq.com -# Gitea Git Provider -GITEA_TOKEN=your-gitea-api-token -GITEA_OWNER=LinumIQ -GITEA_REPO=docs -GITEA_URL=https://git.linumiq.com +# Git branch TINA_GIT_BRANCH=main # Redis (internal docker network) diff --git a/Dockerfile b/Dockerfile index 36b4bae..86be63f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM node:22-alpine AS base RUN corepack enable -RUN apk add --no-cache libc6-compat +RUN apk add --no-cache libc6-compat git # --- Dependencies stage --- FROM base AS deps @@ -41,6 +41,14 @@ COPY --from=builder /app/public ./public COPY --from=builder /app/content ./content COPY --from=builder /app/tina ./tina +# Initialize a local git repo for IsomorphicBridge +# IsomorphicBridge requires a .git directory with at least one commit +RUN git init && \ + git config user.email "cms@linumiq.com" && \ + git config user.name "TinaCMS" && \ + git add -A && \ + git commit -m "Initial content" + # Copy the startup indexing script COPY --from=builder /app/scripts/index-database.mjs ./scripts/index-database.mjs @@ -48,7 +56,9 @@ COPY --from=builder /app/scripts/index-database.mjs ./scripts/index-database.mjs COPY --from=builder /app/.next/static/pagefind ./.next/static/pagefind # Create cache directory with correct permissions -RUN mkdir -p .next/cache && chown -R nextjs:nodejs .next/cache +# IsomorphicBridge needs write access to .git and content for commits +RUN mkdir -p .next/cache && \ + chown -R nextjs:nodejs .next/cache .git content tina USER nextjs diff --git a/docker-compose.yml b/docker-compose.yml index 588f305..28619ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,10 +9,6 @@ services: - TINA_PUBLIC_IS_LOCAL=false - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} - - GITEA_TOKEN=${GITEA_TOKEN} - - GITEA_OWNER=${GITEA_OWNER:-LinumIQ} - - GITEA_REPO=${GITEA_REPO:-docs} - - GITEA_URL=${GITEA_URL:-https://git.linumiq.com} - TINA_GIT_BRANCH=${TINA_GIT_BRANCH:-main} - KV_REST_API_URL=http://redis-http:80 - KV_REST_API_TOKEN=${KV_REST_API_TOKEN} diff --git a/package.json b/package.json index 108c9ab..7f59653 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "date-fns": "^4.1.0", "fast-glob": "^3.3.3", "html-to-md": "^0.8.8", - "js-base64": "^3.7.8", "lodash": "^4.17.21", "mermaid": "^11.6.0", "monaco-editor": "^0.52.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c420dd..1c3e36b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,9 +41,6 @@ importers: html-to-md: specifier: ^0.8.8 version: 0.8.8 - js-base64: - specifier: ^3.7.8 - version: 3.7.8 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -4915,9 +4912,6 @@ packages: react: optional: true - js-base64@3.7.8: - resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} - js-cookie@2.2.1: resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} @@ -12788,8 +12782,6 @@ snapshots: '@types/react': 19.2.9 react: 19.2.3 - js-base64@3.7.8: {} - js-cookie@2.2.1: {} js-sha1@0.6.0: {} diff --git a/tina/database.ts b/tina/database.ts index 4e258c4..18e77e9 100644 --- a/tina/database.ts +++ b/tina/database.ts @@ -1,45 +1,44 @@ import { createDatabase, createLocalDatabase, - FilesystemBridge, + IsomorphicBridge, } from "@tinacms/datalayer"; import { RedisLevel } from "upstash-redis-level"; import { Redis } from "@upstash/redis"; -import { GiteaGitProvider } from "./gitea-git-provider"; const isLocal = process.env.TINA_PUBLIC_IS_LOCAL === "true"; const branch = process.env.TINA_GIT_BRANCH || "main"; function createProductionDatabase() { - const giteaUrl = process.env.GITEA_URL; - const giteaToken = process.env.GITEA_TOKEN; - const giteaOwner = process.env.GITEA_OWNER; - const giteaRepo = process.env.GITEA_REPO; const kvUrl = process.env.KV_REST_API_URL; const kvToken = process.env.KV_REST_API_TOKEN; - if (!giteaUrl || !giteaToken || !giteaOwner || !giteaRepo) { + if (!kvUrl || !kvToken) { // During tinacms build (schema generation), env vars may not be available. // Fall back to local database for the build step. return createLocalDatabase(); } return createDatabase({ - gitProvider: new GiteaGitProvider({ - owner: giteaOwner, - repo: giteaRepo, - token: giteaToken, - branch, - baseUrl: giteaUrl, - }), + gitProvider: { + onPut: async () => {}, + onDelete: async () => {}, + }, databaseAdapter: new RedisLevel>({ redis: new Redis({ - url: kvUrl || "http://localhost:8079", - token: kvToken || "example_token", + url: kvUrl, + token: kvToken, }) as any, debug: process.env.DEBUG === "true" || false, }), - bridge: new FilesystemBridge(process.cwd()), + bridge: new IsomorphicBridge(process.cwd(), { + gitRoot: process.cwd(), + author: { + name: "TinaCMS", + email: "cms@linumiq.com", + }, + commitMessage: "Content updated via TinaCMS", + }), namespace: branch, }); } diff --git a/tina/gitea-git-provider.ts b/tina/gitea-git-provider.ts deleted file mode 100644 index 435315b..0000000 --- a/tina/gitea-git-provider.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Base64 } from "js-base64"; -import type { GitProvider } from "@tinacms/datalayer"; - -export interface GiteaGitProviderOptions { - owner: string; - repo: string; - token: string; - branch: string; - baseUrl: string; - commitMessage?: string; - rootPath?: string; -} - -export class GiteaGitProvider implements GitProvider { - owner: string; - repo: string; - branch: string; - baseUrl: string; - commitMessage: string; - rootPath?: string; - private token: string; - - constructor(args: GiteaGitProviderOptions) { - this.owner = args.owner; - this.repo = args.repo; - this.branch = args.branch; - this.baseUrl = args.baseUrl.replace(/\/$/, ""); - this.commitMessage = args.commitMessage || "Edited with TinaCMS"; - this.rootPath = args.rootPath; - this.token = args.token; - } - - private getApiUrl(path: string): string { - return `${this.baseUrl}/api/v1/repos/${this.owner}/${this.repo}/contents/${path}`; - } - - private getHeaders(): Record { - return { - Authorization: `token ${this.token}`, - "Content-Type": "application/json", - Accept: "application/json", - }; - } - - private getKeyWithPath(key: string): string { - return this.rootPath ? `${this.rootPath}/${key}` : key; - } - - private async getFileSha( - keyWithPath: string - ): Promise { - try { - const url = `${this.getApiUrl(keyWithPath)}?ref=${encodeURIComponent(this.branch)}`; - const response = await fetch(url, { - method: "GET", - headers: this.getHeaders(), - }); - if (!response.ok) { - return undefined; - } - const data = await response.json(); - return data.sha; - } catch { - return undefined; - } - } - - async onPut(key: string, value: string): Promise { - const keyWithPath = this.getKeyWithPath(key); - const sha = await this.getFileSha(keyWithPath); - - const body: Record = { - message: this.commitMessage, - content: Base64.encode(value), - branch: this.branch, - }; - - if (sha) { - body.sha = sha; - } - - const response = await fetch(this.getApiUrl(keyWithPath), { - method: sha ? "PUT" : "POST", - headers: this.getHeaders(), - body: JSON.stringify(body), - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `Failed to save ${keyWithPath}: ${response.status} ${errorText}` - ); - } - } - - async onDelete(key: string): Promise { - const keyWithPath = this.getKeyWithPath(key); - const sha = await this.getFileSha(keyWithPath); - - if (!sha) { - throw new Error( - `Could not find file ${keyWithPath} in repo ${this.owner}/${this.repo}` - ); - } - - const response = await fetch(this.getApiUrl(keyWithPath), { - method: "DELETE", - headers: this.getHeaders(), - body: JSON.stringify({ - message: this.commitMessage, - branch: this.branch, - sha, - }), - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `Failed to delete ${keyWithPath}: ${response.status} ${errorText}` - ); - } - } -}