Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pasquin

Anonymous notes, shared instantly. Write a note, get a shareable link ! No account required. Optionally protect it with a password or set it to auto-expire.

🔗 Live demo: https://pasquin.mkgg.dev

A pasquinade is an anonymous note posted in a public place which is named after "Pasquino," a statue in Renaissance Rome where people stuck up anonymous verses. That's exactly what this app is: anonymous notes, posted to a public link.

Screenshots

The rich-text editor Share dialog with QR code Viewing a shared note
The editor Share dialog with QR code Reading a shared note

Features

  • Rich text editor — headings, lists, task lists, code blocks, highlighting, text color, links (built on TipTap).
  • Anonymous ownership — no sign-up. Each note returns a one-time edit key that proves ownership; the server only ever stores its hash.
  • Shareable links — view-only by default, or an editable link that carries the key in the URL fragment (never sent to the server).
  • Password protection — reader-facing passwords, hashed with BCrypt. Protected notes hide their title and content until unlocked.
  • Auto-expiry — 1 hour to 1 month, implemented server-side with DynamoDB TTL (the note deletes itself).
  • My Notes — a per-browser sidebar (search, rename, delete, copy link) backed by localStorage.
  • Google sign-in (optional) — syncs the note list across browsers and devices.
  • Premium image uploads — a one-time purchase (via Polar as merchant of record, confirmed by signed webhook) unlocks embedding images in notes. Uploads are validated by magic bytes, capped per image and per user, and served from the CDN.
  • Sharing extras — one-click copy, email, and a generated QR code for handing a note to a phone.
  • Abuse reporting & contact — public forms with validation, rate limiting, and a spam honeypot. Submissions are emailed to the operator via SES.
  • Moderation tooling — an operator-only takedown endpoint removes a reported note everywhere at once: metadata, content, embedded images, and CloudFront edge caches.
  • Dark / light theme, responsive down to mobile.

Architecture

Architecture diagram

Static frontend served globally from CloudFront + S3; a containerized Spring Boot API on App Runner persists note metadata to DynamoDB and note bodies to S3. Premium images live in the content bucket behind a dedicated CloudFront images/* behavior, contact/report submissions are relayed by SES, and purchases go through Polar (checkout API out, signed order.paid webhooks in). Storage cleanup is event-driven: removing a note (TTL expiry or delete) emits a DynamoDB Streams event, and a Lambda sweeps the orphaned S3 body, its images, and their CDN caches. An AWS Budgets alarm emails the operator before costs can creep. Everything is defined in Terraform.

CI/CD

flowchart LR
    Push([git push main]) --> GHA[GitHub Actions]
    GHA -->|OIDC · no stored keys| Role[(AWS deploy role)]
    GHA -->|build + push image| ECR[ECR] -.->|auto-deploy| API[App Runner]
    GHA -->|sync + invalidate| CDN[S3 + CloudFront]
Loading

Push to main and the changed app deploys itself. GitHub authenticates to AWS via OIDC — there are no long-lived AWS credentials stored anywhere.

Tech stack

Layer Tech
Frontend Astro 5, React 19, TipTap 3, Tailwind CSS 4, shadcn/ui, TypeScript
Backend Spring Boot 4, Java 25, AWS SDK v2, BCrypt, Bucket4j (rate limiting)
Data DynamoDB (on-demand, TTL), S3
Billing Polar (merchant of record) · Standard-Webhooks signature verification
Infra Terraform · App Runner, CloudFront, ECR, DynamoDB (+ Streams), S3, Lambda, SES, IAM, Budgets
CI/CD GitHub Actions with OIDC federation
Quality Spotless (Google Java Format), astro check, JUnit

How anonymous ownership works

There are no user accounts, yet notes are still editable and deletable only by their creator:

  1. On save, the server generates a random 128-bit edit key, returns it once, and stores only its SHA-256 hash.
  2. Updates and deletes require the key in an X-Edit-Key header; a wrong key gets a 403.
  3. The browser keeps a slug → key map in localStorage, that map is the "My Notes" list.
  4. Editable share links put the key in the URL fragment (/n/{slug}#key), which browsers never transmit to a server, so the key stays private even in transit.

Local development

Backend (runs on :8080 with an in-memory store — no AWS needed):

cd backend
./mvnw spring-boot:run

Frontend (runs on :1234, proxies to the local backend):

cd frontend
npm install
npm run dev

Run the backend test suite:

cd backend
./mvnw verify   # unit tests + Spotless format check

Deployment

Infrastructure is fully codified in infra/ (Terraform). See infra/README.md for the step-by-step deploy runbook and cost notes. In short: Terraform provisions everything, and GitHub Actions handles ongoing deploys.

Project layout

pasquin/
├─ frontend/   # Astro + React app
├─ backend/    # Spring Boot API
├─ infra/      # Terraform (AWS)
└─ .github/    # CI/CD workflows

License

This project is open source and available under the MIT License.

About

Post anonymous notes and share it to everyone!

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages