Skip to content

Commit 18de2e9

Browse files
chenhaiyunclaude
andcommitted
Initial commit: Telegram Group Photo Wall
Real-time photo wall displaying messages and images from Telegram groups. Features: quantum signatures via AWS Braket SV1, draggable cards, admin dashboard, multi-group support, ECS Fargate + CloudFront deployment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0 parents  commit 18de2e9

44 files changed

Lines changed: 5338 additions & 0 deletions

Some content is hidden

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

.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# CDK
5+
cdk.out/
6+
cdk.context.json
7+
8+
# Next.js
9+
.next/
10+
out/
11+
dist/
12+
build/
13+
14+
# CDK compiled output (root level only)
15+
/*.js
16+
/*.d.ts
17+
/*.js.map
18+
/bin/*.js
19+
/bin/*.d.ts
20+
/lib/*.js
21+
/lib/*.d.ts
22+
23+
# Environment files
24+
.env
25+
.env.*
26+
*.secret
27+
28+
# OS
29+
.DS_Store
30+
31+
# IDE
32+
.vscode/
33+
34+
# Logs
35+
*.log
36+
37+
# Lock files in subdirectories (root lock file is kept)
38+
photo-wall/package-lock.json

README.md

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
# Telegram Group Photo Wall
2+
3+
Real-time photo wall that displays messages and images from Telegram groups. Each group gets its own dedicated photo wall page. Messages appear as colorful sticky notes with quantum signatures generated by AWS Braket SV1.
4+
5+
## Architecture
6+
7+
```
8+
Telegram Bot → CloudFront (wall.example.com)
9+
→ ALB → ECS Fargate (Next.js)
10+
├── DynamoDB (messages + positions)
11+
├── S3 (photos + Braket results)
12+
├── Secrets Manager (bot tokens, admin password)
13+
└── AWS Braket SV1 (quantum signatures)
14+
```
15+
16+
- **ECS Fargate** running Next.js (standalone Docker)
17+
- **ALB** with CloudFront secret header validation (blocks direct access)
18+
- **CloudFront** with custom domain + ACM certificate
19+
- **Route53** DNS alias record → CloudFront
20+
- **DynamoDB** for message storage (pay-per-request, encrypted)
21+
- **S3** for photo storage (private, encrypted, versioned)
22+
- **AWS Braket SV1** for quantum random number + Bell state measurement
23+
24+
## Features
25+
26+
- Colorful sticky note wall with sci-fi background
27+
- Real-time 5-second polling for new messages
28+
- Only messages that **@mention the bot** are shown on the wall
29+
- Quantum signature per user (first message triggers Braket SV1, subsequent reuse)
30+
- Draggable cards with position persistence (percentage-based, responsive)
31+
- Fullscreen layout, responsive to window resize
32+
- Event logo overlay at top center
33+
- User leaderboard at bottom (configurable count)
34+
- Admin dashboard with password login
35+
- Soft-delete (hide) individual or all messages
36+
- Custom domain support via Route53 + ACM
37+
38+
## Prerequisites
39+
40+
- Node.js 20+
41+
- AWS CDK CLI (`npm install -g aws-cdk`)
42+
- AWS account with CDK bootstrapped (`cdk bootstrap`)
43+
- Docker (for building ECS container image)
44+
- Telegram Bot token(s) created via [@BotFather](https://t.me/BotFather)
45+
46+
## Setup
47+
48+
### 1. Store Bot Tokens in AWS Secrets Manager
49+
50+
```bash
51+
aws secretsmanager create-secret \
52+
--name "telegram/bot-token/demo-group" \
53+
--secret-string "YOUR_BOT_TOKEN_HERE" \
54+
--region us-west-2
55+
```
56+
57+
### 2. Configure Groups in `cdk.json`
58+
59+
```json
60+
{
61+
"context": {
62+
"telegramGroups": [
63+
{
64+
"groupId": "my-team",
65+
"chatId": "-1001234567890",
66+
"name": "My Team Photo Wall",
67+
"secretName": "telegram/bot-token/my-team",
68+
"botUsername": "my_photo_wall_bot"
69+
}
70+
]
71+
}
72+
}
73+
```
74+
75+
| Field | Description | Example |
76+
|-------|-------------|---------|
77+
| `groupId` | URL identifier for the wall | `my-team` |
78+
| `chatId` | Telegram group Chat ID | `-1001234567890` |
79+
| `name` | Display name on the wall | `My Team Photo Wall` |
80+
| `secretName` | Secrets Manager name for bot token | `telegram/bot-token/my-team` |
81+
| `botUsername` | Bot's @username (only @mentions shown) | `my_photo_wall_bot` |
82+
83+
### 3. Configure Custom Domain (Optional)
84+
85+
If you want a custom domain like `wall.example.com`:
86+
87+
#### a. Request ACM Certificate (must be in us-east-1)
88+
89+
```bash
90+
aws acm request-certificate \
91+
--domain-name "wall.example.com" \
92+
--validation-method DNS \
93+
--region us-east-1
94+
```
95+
96+
#### b. Validate via Route53
97+
98+
```bash
99+
# Get the DNS validation record from ACM, then add to Route53
100+
# See docs/telegram-integration-guide.md for detailed steps
101+
```
102+
103+
#### c. Add Domain Config to `cdk.json`
104+
105+
```json
106+
{
107+
"context": {
108+
"domain": {
109+
"name": "wall.example.com",
110+
"hostedZoneId": "ZXXXXXXXXXXXXX",
111+
"hostedZoneName": "example.com",
112+
"certificateArn": "arn:aws:acm:us-east-1:123456789:certificate/xxx-xxx"
113+
},
114+
"telegramGroups": [...]
115+
}
116+
}
117+
```
118+
119+
| Field | Description |
120+
|-------|-------------|
121+
| `domain.name` | Full subdomain (e.g. `wall.example.com`) |
122+
| `domain.hostedZoneId` | Route53 Hosted Zone ID |
123+
| `domain.hostedZoneName` | Root domain (e.g. `example.com`) |
124+
| `domain.certificateArn` | ACM certificate ARN (must be in **us-east-1**) |
125+
126+
CDK will automatically create a Route53 A record alias pointing to CloudFront.
127+
128+
### 4. Deploy
129+
130+
```bash
131+
./deploy.sh
132+
```
133+
134+
Or manually:
135+
136+
```bash
137+
npm install
138+
cd photo-wall && npm install && npm run build && cd ..
139+
npx cdk deploy
140+
```
141+
142+
### 5. Set Telegram Webhook
143+
144+
```bash
145+
WEBHOOK_SECRET=$(aws secretsmanager get-secret-value \
146+
--secret-id telegram/webhook-secret \
147+
--query SecretString --output text \
148+
--region us-west-2)
149+
150+
curl -X POST "https://api.telegram.org/bot<BOT_TOKEN>/setWebhook" \
151+
-H "Content-Type: application/json" \
152+
-d '{
153+
"url": "https://<YOUR_CLOUDFRONT_DOMAIN>/api/webhook/demo-group",
154+
"secret_token": "'$WEBHOOK_SECRET'",
155+
"allowed_updates": ["message"]
156+
}'
157+
```
158+
159+
### 6. Add Bot to Group
160+
161+
1. Add your bot to the Telegram group
162+
2. Turn off **Privacy Mode** in BotFather (Bot Settings → Group Privacy → Turn off)
163+
3. Users send messages with `@your_bot_username` to have them appear on the wall
164+
165+
## Admin Dashboard
166+
167+
Access: `https://<YOUR_CLOUDFRONT_DOMAIN>/admin`
168+
169+
### Get Admin Password
170+
171+
```bash
172+
aws secretsmanager get-secret-value \
173+
--secret-id "telegram/admin-password" \
174+
--query SecretString --output text \
175+
--region us-west-2
176+
```
177+
178+
### Change Admin Password
179+
180+
```bash
181+
aws secretsmanager update-secret \
182+
--secret-id "telegram/admin-password" \
183+
--secret-string "your-new-password" \
184+
--region us-west-2
185+
```
186+
187+
### Admin Features
188+
189+
- **Login** — Password-protected admin page
190+
- **Message list** — View all messages with sender, text, type, quantum signature status
191+
- **Hide message** — Soft-delete individual messages (data preserved in DynamoDB)
192+
- **Clear all** — Soft-delete all messages for a group
193+
- **Group selector** — Switch between groups (if multiple configured)
194+
195+
## Quantum Signatures
196+
197+
Each user gets a unique quantum signature generated by **AWS Braket SV1** quantum simulator:
198+
199+
1. **First message** from a sender → submits 2 quantum tasks to Braket SV1:
200+
- 4-qubit random number circuit (H + CNOT + Ry rotations, 100 shots)
201+
- 2-qubit Bell state circuit (|Φ+⟩, 200 shots)
202+
2. **Subsequent messages** from the same sender → reuse the existing signature (no Braket call)
203+
3. Signature displayed as `Q#452 | 7B284BB3D413` on each card
204+
4. Hover to see full details (quantum number, public key, signature, device, algorithm)
205+
5. If Braket fails, gracefully falls back to local crypto-based signature
206+
207+
## ECS Health Check
208+
209+
- **Path**: `/api/health`
210+
- **Interval**: 15s, **Timeout**: 5s
211+
- **Healthy/Unhealthy threshold**: 2
212+
213+
## Security
214+
215+
- Bot tokens in Secrets Manager (never in code/env)
216+
- Admin password in Secrets Manager (auto-generated)
217+
- Webhook requests validated via Telegram secret token
218+
- ALB restricted to CloudFront IPs via AWS prefix list
219+
- CloudFront secret header prevents direct ALB access
220+
- Custom domain with ACM TLS certificate
221+
- S3 fully private (no public access)
222+
- DynamoDB/S3 encrypted at rest
223+
- HTTPS everywhere (CloudFront → viewer)
224+
- Input sanitization (XSS prevention)
225+
- Security headers: HSTS, X-Frame-Options, CSP, XSS protection
226+
- IAM least-privilege for ECS task role
227+
- Container runs as non-root user
228+
229+
## Other Docs
230+
231+
- [Telegram Integration Guide](docs/telegram-integration-guide.md) — Detailed Telegram setup
232+
- [Add New Group](docs/add-new-group.md) — Create a new group photo wall
233+
- [Local Development](docs/local-development.md) — Local dev setup
234+
235+
## Project Structure
236+
237+
```
238+
telegram-photo-wall/
239+
├── bin/app.ts # CDK entry
240+
├── lib/telegram-photo-wall-stack.ts # Infrastructure
241+
├── photo-wall/ # Next.js application
242+
│ ├── src/
243+
│ │ ├── app/
244+
│ │ │ ├── page.tsx # Home (redirect)
245+
│ │ │ ├── wall/[groupId]/page.tsx # Photo wall page
246+
│ │ │ ├── admin/page.tsx # Admin dashboard
247+
│ │ │ └── api/
248+
│ │ │ ├── health/route.ts # Health check
249+
│ │ │ ├── webhook/[groupId]/ # Telegram webhook
250+
│ │ │ ├── messages/[groupId]/ # Messages CRUD + positions
251+
│ │ │ ├── groups/route.ts # Groups list
252+
│ │ │ └── admin/route.ts # Admin login + actions
253+
│ │ ├── components/
254+
│ │ │ ├── PhotoWall.tsx # Main wall with polling + drag
255+
│ │ │ ├── MessageCard.tsx # Sticky note card + quantum badge
256+
│ │ │ ├── GroupNav.tsx # Group switcher
257+
│ │ │ └── Lightbox.tsx # Image preview
258+
│ │ └── lib/
259+
│ │ ├── aws.ts # AWS SDK clients
260+
│ │ ├── config.ts # Group config
261+
│ │ ├── sanitize.ts # Input sanitization
262+
│ │ └── quantum-signature.ts # Braket SV1 quantum signature
263+
│ ├── public/logo.png # Event banner logo
264+
│ ├── Dockerfile # Multi-stage Node 20 Alpine
265+
│ └── package.json
266+
├── scripts/
267+
│ ├── setup-local-env.sh # Generate .env.local from CDK
268+
│ ├── set-webhook-local.sh # Webhook → ngrok (local dev)
269+
│ └── set-webhook-prod.sh # Webhook → production
270+
├── docs/
271+
│ ├── requirements.md # Requirements document
272+
│ ├── telegram-integration-guide.md # Telegram setup guide
273+
│ ├── add-new-group.md # Add new group guide
274+
│ └── local-development.md # Local dev guide
275+
├── deploy.sh # One-click deployment
276+
├── cdk.json # CDK + group + domain config
277+
└── package.json # CDK dependencies
278+
```

bin/app.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env node
2+
import "source-map-support/register";
3+
import * as cdk from "aws-cdk-lib";
4+
import { TelegramPhotoWallStack } from "../lib/telegram-photo-wall-stack";
5+
6+
const app = new cdk.App();
7+
8+
const telegramGroups = app.node.tryGetContext("telegramGroups") ?? [];
9+
const domainConfig = app.node.tryGetContext("domain") ?? {};
10+
11+
new TelegramPhotoWallStack(app, "TelegramPhotoWallStack", {
12+
telegramGroups,
13+
domainName: domainConfig.name,
14+
hostedZoneId: domainConfig.hostedZoneId,
15+
hostedZoneName: domainConfig.hostedZoneName,
16+
certificateArn: domainConfig.certificateArn,
17+
env: {
18+
account: process.env.CDK_DEFAULT_ACCOUNT,
19+
region: process.env.CDK_DEFAULT_REGION,
20+
},
21+
});

cdk.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/app.ts",
3+
"watch": {
4+
"include": ["**"],
5+
"exclude": [
6+
"README.md",
7+
"cdk*.json",
8+
"**/*.d.ts",
9+
"**/*.js",
10+
"tsconfig.json",
11+
"package*.json",
12+
"node_modules",
13+
"photo-wall/node_modules",
14+
"photo-wall/.next"
15+
]
16+
},
17+
"context": {
18+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
19+
"@aws-cdk/core:checkSecretUsage": true,
20+
"@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
21+
"domain": {
22+
"name": "wall.example.com",
23+
"hostedZoneId": "ZXXXXXXXXXXXXX",
24+
"hostedZoneName": "example.com",
25+
"certificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
26+
},
27+
"telegramGroups": [
28+
{
29+
"groupId": "demo-group",
30+
"chatId": "-1001234567890",
31+
"name": "Demo Photo Wall",
32+
"secretName": "telegram/bot-token/demo-group",
33+
"botUsername": "your_bot_username"
34+
}
35+
]
36+
}
37+
}

0 commit comments

Comments
 (0)