Skip to content

Commit abddf82

Browse files
author
ChasingImpact
committed
Initial commit: ged2json.com GEDCOM to JSON converter
0 parents  commit abddf82

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+10930
-0
lines changed

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
NEXT_PUBLIC_APP_URL=http://localhost:3000
2+
RATE_LIMIT_MAX=100
3+
RATE_LIMIT_WINDOW=86400000
4+
FILE_DELETION_TIMEOUT=3600000
5+

.eslintrc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}
4+

.github/workflows/test.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v3
15+
16+
- name: Setup Node.js
17+
uses: actions/setup-node@v3
18+
with:
19+
node-version: '18'
20+
cache: 'npm'
21+
22+
- name: Install dependencies
23+
run: npm ci
24+
25+
- name: Run linter
26+
run: npm run lint
27+
28+
- name: Build
29+
run: npm run build
30+

.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pkg
6+
/.next
7+
/out
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
.env
31+
32+
# vercel
33+
.vercel
34+
35+
# typescript
36+
*.tsbuildinfo
37+
next-env.d.ts
38+
39+
# temp files
40+
/tmp
41+

README.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# ged2json.com
2+
3+
A privacy-first GEDCOM to JSON converter. Files are automatically deleted after 60 minutes.
4+
5+
**Production Site:** https://ged2json.com/
6+
7+
**API Documentation:** https://ged2json.com/developers
8+
9+
## Features
10+
11+
- Bidirectional conversion between GEDCOM and JSON formats
12+
- Zero data retention - files auto-delete after 60 minutes
13+
- Rate limiting (100 conversions per day per IP)
14+
- Family tree analytics and visualization
15+
- Dark/light theme support
16+
- Mobile responsive design
17+
18+
## Tech Stack
19+
20+
- Next.js 14 (App Router)
21+
- React 18
22+
- TypeScript
23+
- Tailwind CSS
24+
- Radix UI components
25+
26+
## Getting Started
27+
28+
### Prerequisites
29+
30+
- Node.js 18+
31+
- npm
32+
33+
### Installation
34+
35+
```bash
36+
npm install
37+
```
38+
39+
### Development
40+
41+
```bash
42+
npm run dev
43+
```
44+
45+
Open http://localhost:3000 in your browser.
46+
47+
### Build
48+
49+
```bash
50+
npm run build
51+
npm start
52+
```
53+
54+
## Environment Variables
55+
56+
Copy `.env.example` to `.env.local` and configure:
57+
58+
```
59+
NEXT_PUBLIC_APP_URL=http://localhost:3000
60+
RATE_LIMIT_MAX=100
61+
RATE_LIMIT_WINDOW=86400000
62+
FILE_DELETION_TIMEOUT=3600000
63+
```
64+
65+
## API Usage
66+
67+
### Convert GEDCOM to JSON
68+
69+
```bash
70+
curl -X POST https://ged2json.com/api/convert \
71+
72+
-F "direction=ged-to-json"
73+
```
74+
75+
### Convert JSON to GEDCOM
76+
77+
```bash
78+
curl -X POST https://ged2json.com/api/convert \
79+
80+
-F "direction=json-to-ged"
81+
```
82+
83+
### Response
84+
85+
```json
86+
{
87+
"success": true,
88+
"fileId": "uuid",
89+
"treeUrl": "https://ged2json.com/api/download/{fileId}/tree",
90+
"jsonUrl": "https://ged2json.com/api/download/{fileId}/json",
91+
"deleteIn": "59:59",
92+
"remaining": 99
93+
}
94+
```
95+
96+
## Project Structure
97+
98+
```
99+
ged2json/
100+
|-- app/
101+
| |-- api/
102+
| | |-- convert/ # Main conversion endpoint
103+
| | |-- download/ # File download handler
104+
| | +-- email/ # Email collection
105+
| |-- layout.tsx
106+
| +-- page.tsx
107+
|-- components/
108+
| |-- ui/ # Radix UI components
109+
| |-- UploadZone.tsx
110+
| |-- TreeVisualizer.tsx
111+
| |-- JSONPreview.tsx
112+
| +-- ...
113+
|-- lib/
114+
| |-- parser.ts # GEDCOM/JSON conversion
115+
| |-- storage.ts # Temp file management
116+
| |-- rate-limit.ts # Rate limiting
117+
| |-- analytics.ts # Family tree analytics
118+
| +-- types.ts # TypeScript types
119+
+-- tmp/ # Temporary file storage
120+
```
121+
122+
## Supported GEDCOM Features
123+
124+
- Individuals (names, genders, dates, places)
125+
- Families (marriages, divorces, children)
126+
- Events (birth, death, marriage, etc.)
127+
- Sources and citations
128+
- Repositories
129+
- Notes
130+
- Multimedia links
131+
- GEDCOM 5.5.1, 5.5.5, and 7.0 features
132+
133+
## Deployment
134+
135+
### Vercel (Recommended)
136+
137+
1. Push your code to GitHub
138+
2. Import project in Vercel
139+
3. Deploy
140+
141+
### Other Platforms
142+
143+
The app can be deployed to any platform that supports Next.js:
144+
- Netlify
145+
- Railway
146+
- Render
147+
- AWS Amplify
148+
- Self-hosted
149+
150+
## License
151+
152+
MIT

app/api/convert/route.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { checkRateLimit } from '@/lib/rate-limit';
3+
import { saveFile, saveConvertedFiles, getTimeUntilDeletion, getFileMetadata } from '@/lib/storage';
4+
import { gedcomToJson, jsonToGedcom } from '@/lib/parser';
5+
6+
export const runtime = 'nodejs';
7+
export const maxDuration = 30;
8+
9+
export async function POST(request: NextRequest) {
10+
try {
11+
const rateLimit = checkRateLimit(request);
12+
if (!rateLimit.allowed) {
13+
return NextResponse.json(
14+
{
15+
success: false,
16+
error: 'Rate limit exceeded. Maximum 100 conversions per day.',
17+
resetTime: rateLimit.resetTime,
18+
},
19+
{ status: 429 }
20+
);
21+
}
22+
23+
const formData = await request.formData();
24+
const file = formData.get('file') as File;
25+
const direction = formData.get('direction') as string;
26+
27+
if (!file) {
28+
return NextResponse.json(
29+
{ success: false, error: 'No file provided' },
30+
{ status: 400 }
31+
);
32+
}
33+
34+
if (!direction || !['ged-to-json', 'json-to-ged'].includes(direction)) {
35+
return NextResponse.json(
36+
{ success: false, error: 'Invalid direction. Must be "ged-to-json" or "json-to-ged"' },
37+
{ status: 400 }
38+
);
39+
}
40+
41+
const arrayBuffer = await file.arrayBuffer();
42+
const buffer = Buffer.from(arrayBuffer);
43+
const fileContent = buffer.toString('utf-8');
44+
45+
const fileId = saveFile(buffer, file.name, direction as 'ged-to-json' | 'json-to-ged');
46+
47+
let jsonContent: string | undefined;
48+
let gedcomContent: string | undefined;
49+
50+
try {
51+
if (direction === 'ged-to-json') {
52+
const jsonData = gedcomToJson(fileContent);
53+
jsonContent = JSON.stringify(jsonData, null, 2);
54+
saveConvertedFiles(fileId, jsonContent);
55+
} else {
56+
const jsonData = JSON.parse(fileContent);
57+
jsonContent = JSON.stringify(jsonData, null, 2);
58+
gedcomContent = jsonToGedcom(jsonData);
59+
saveConvertedFiles(fileId, jsonContent, gedcomContent);
60+
}
61+
} catch (error: any) {
62+
return NextResponse.json(
63+
{
64+
success: false,
65+
error: `Conversion failed: ${error.message || 'Invalid file format'}`,
66+
},
67+
{ status: 400 }
68+
);
69+
}
70+
71+
const timeRemaining = getTimeUntilDeletion(fileId);
72+
const metadata = getFileMetadata(fileId);
73+
const uploadedAt = metadata?.uploadedAt || Date.now();
74+
75+
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
76+
const treeUrl = `${baseUrl}/api/download/${fileId}/tree`;
77+
const jsonUrl = direction === 'ged-to-json'
78+
? `${baseUrl}/api/download/${fileId}/json`
79+
: undefined;
80+
const gedUrl = direction === 'json-to-ged'
81+
? `${baseUrl}/api/download/${fileId}/ged`
82+
: undefined;
83+
84+
return NextResponse.json({
85+
success: true,
86+
fileId,
87+
treeUrl,
88+
jsonUrl,
89+
gedUrl,
90+
uploadedAt,
91+
deleteIn: `${Math.floor(timeRemaining / 60000)}:${Math.floor((timeRemaining % 60000) / 1000).toString().padStart(2, '0')}`,
92+
remaining: rateLimit.remaining,
93+
});
94+
} catch (error: any) {
95+
console.error('Conversion error:', error);
96+
return NextResponse.json(
97+
{
98+
success: false,
99+
error: error.message || 'Internal server error',
100+
},
101+
{ status: 500 }
102+
);
103+
}
104+
}
105+

0 commit comments

Comments
 (0)