diff --git a/apps/documentation/README.md b/apps/documentation/README.md index adf7cb8a7..66280ee92 100644 --- a/apps/documentation/README.md +++ b/apps/documentation/README.md @@ -1,36 +1 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3002](http://localhost:3002) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +# @documenso/documentation diff --git a/apps/openpage-api/.gitignore b/apps/openpage-api/.gitignore new file mode 100644 index 000000000..26b002aac --- /dev/null +++ b/apps/openpage-api/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for commiting if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/openpage-api/README.md b/apps/openpage-api/README.md new file mode 100644 index 000000000..41c767aa2 --- /dev/null +++ b/apps/openpage-api/README.md @@ -0,0 +1 @@ +# @documenso/openpage-api diff --git a/apps/openpage-api/app/github/forks/route.ts b/apps/openpage-api/app/github/forks/route.ts new file mode 100644 index 000000000..f13d0503c --- /dev/null +++ b/apps/openpage-api/app/github/forks/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; + +import { requestHandler } from '@/app/request-handler'; + +export const GET = requestHandler(async () => { + const res = await fetch('https://api.github.com/repos/documenso/documenso'); + const { forks_count } = await res.json(); + + return NextResponse.json({ + data: forks_count, + }); +}); diff --git a/apps/openpage-api/app/github/issues/route.ts b/apps/openpage-api/app/github/issues/route.ts new file mode 100644 index 000000000..a55878421 --- /dev/null +++ b/apps/openpage-api/app/github/issues/route.ts @@ -0,0 +1,14 @@ +import { NextResponse } from 'next/server'; + +import { requestHandler } from '@/app/request-handler'; + +export const GET = requestHandler(async () => { + const res = await fetch( + 'https://api.github.com/search/issues?q=repo:documenso/documenso+type:issue+state:open&page=0&per_page=1', + ); + const { total_count } = await res.json(); + + return NextResponse.json({ + data: total_count, + }); +}); diff --git a/apps/openpage-api/app/github/prs/route.ts b/apps/openpage-api/app/github/prs/route.ts new file mode 100644 index 000000000..fe0b8505c --- /dev/null +++ b/apps/openpage-api/app/github/prs/route.ts @@ -0,0 +1,14 @@ +import { NextResponse } from 'next/server'; + +import { requestHandler } from '@/app/request-handler'; + +export const GET = requestHandler(async () => { + const res = await fetch( + 'https://api.github.com/search/issues?q=repo:documenso/documenso/+is:pr+merged:>=2010-01-01&page=0&per_page=1', + ); + const { total_count } = await res.json(); + + return NextResponse.json({ + data: total_count, + }); +}); diff --git a/apps/openpage-api/app/github/route.ts b/apps/openpage-api/app/github/route.ts new file mode 100644 index 000000000..ddce7429a --- /dev/null +++ b/apps/openpage-api/app/github/route.ts @@ -0,0 +1,17 @@ +import { type NextRequest, NextResponse } from 'next/server'; + +const paths = [ + { path: '/forks', description: 'GitHub Forks' }, + { path: '/stars', description: 'GitHub Stars' }, + { path: '/issues', description: 'GitHub Merged Issues' }, + { path: '/prs', description: 'GitHub Pull Request' }, +]; + +export function GET(request: NextRequest) { + const url = request.nextUrl.toString(); + const apis = paths.map(({ path, description }) => { + return { path: url + path, description }; + }); + + return NextResponse.json(apis); +} diff --git a/apps/openpage-api/app/github/stars/route.ts b/apps/openpage-api/app/github/stars/route.ts new file mode 100644 index 000000000..c89cb5c6c --- /dev/null +++ b/apps/openpage-api/app/github/stars/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; + +import { requestHandler } from '@/app/request-handler'; + +export const GET = requestHandler(async () => { + const res = await fetch('https://api.github.com/repos/documenso/documenso'); + const { stargazers_count } = await res.json(); + + return NextResponse.json({ + data: stargazers_count, + }); +}); diff --git a/apps/openpage-api/app/request-handler.ts b/apps/openpage-api/app/request-handler.ts new file mode 100644 index 000000000..12e2193e9 --- /dev/null +++ b/apps/openpage-api/app/request-handler.ts @@ -0,0 +1,43 @@ +import type { NextRequest } from 'next/server'; +import { NextResponse } from 'next/server'; + +type RouteHandler> = ( + req: NextRequest, + ctx: { params: T }, +) => Promise | Response; + +function isAllowedOrigin(req: NextRequest): boolean { + const referer = req.headers.get('referer'); + const host = req.headers.get('host'); + + if (referer && host) { + const refererUrl = new URL(referer); + return refererUrl.host === host; + } + + if (host?.includes('localhost')) { + return true; + } + + return false; +} + +export function requestHandler>( + handler: RouteHandler, +): RouteHandler { + return async (req: NextRequest, ctx: { params: T }) => { + try { + if (!isAllowedOrigin(req)) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); + } + + const result = await handler(req, ctx); + + return result; + } catch (error) { + console.log(error); + + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } + }; +} diff --git a/apps/openpage-api/app/route.ts b/apps/openpage-api/app/route.ts new file mode 100644 index 000000000..d7a3e851b --- /dev/null +++ b/apps/openpage-api/app/route.ts @@ -0,0 +1,20 @@ +import { type NextRequest, NextResponse } from 'next/server'; + +const paths = [{ path: 'github', description: 'GitHub Data' }]; + +export function GET(request: NextRequest) { + const url = request.nextUrl.toString(); + const apis = paths.map(({ path, description }) => { + return { path: url + path, description }; + }); + + return NextResponse.json(apis, { + status: 200, + headers: { + // TODO: Update for marketing page + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }, + }); +} diff --git a/apps/openpage-api/next.config.js b/apps/openpage-api/next.config.js new file mode 100644 index 000000000..658404ac6 --- /dev/null +++ b/apps/openpage-api/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = nextConfig; diff --git a/apps/openpage-api/package.json b/apps/openpage-api/package.json new file mode 100644 index 000000000..367293480 --- /dev/null +++ b/apps/openpage-api/package.json @@ -0,0 +1,21 @@ +{ + "name": "@documenso/openpage-api", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev -p 3003", + "build": "next build", + "start": "next start", + "lint:fix": "next lint --fix", + "clean": "rimraf .next && rimraf node_modules", + "copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs" + }, + "dependencies": { + "next": "14.2.6" + }, + "devDependencies": { + "@types/node": "20.16.5", + "@types/react": "18.3.5", + "typescript": "5.5.4" + } +} diff --git a/apps/openpage-api/tsconfig.json b/apps/openpage-api/tsconfig.json new file mode 100644 index 000000000..d8b93235f --- /dev/null +++ b/apps/openpage-api/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/package-lock.json b/package-lock.json index 0432c882d..7a5d2e30c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -439,6 +439,56 @@ "node": ">=14.17" } }, + "apps/openpage-api": { + "name": "@documenso/openpage-api", + "version": "1.0.0", + "dependencies": { + "next": "14.2.6" + }, + "devDependencies": { + "@types/node": "20.16.5", + "@types/react": "18.3.5", + "typescript": "5.5.4" + } + }, + "apps/openpage-api/node_modules/@types/node": { + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "apps/openpage-api/node_modules/@types/react": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz", + "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "apps/openpage-api/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "apps/openpage-api/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, "apps/web": { "name": "@documenso/web", "version": "1.7.2-rc.1", @@ -2671,6 +2721,10 @@ "nodemailer": "^6.9.3" } }, + "node_modules/@documenso/openpage-api": { + "resolved": "apps/openpage-api", + "link": true + }, "node_modules/@documenso/pdf-sign": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@documenso/pdf-sign/-/pdf-sign-0.1.0.tgz", diff --git a/package.json b/package.json index 2f0e6d146..827d1ece6 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dev:web": "turbo run dev --filter=@documenso/web", "dev:marketing": "turbo run dev --filter=@documenso/marketing", "dev:docs": "turbo run dev --filter=@documenso/documentation", + "dev:openpage-api": "turbo run dev --filter=@documenso/openpage-api", "start": "turbo run start --filter=@documenso/web --filter=@documenso/marketing --filter=@documenso/documentation", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix",