Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Dependencies and compiled code
node_modules
dist

# Secrets (CRITICAL)
.env
.env.*

# Git and Docs
.git
.gitignore
*.md

# Local Package Manager Configs
.npmrc
4 changes: 0 additions & 4 deletions .env

This file was deleted.

9 changes: 3 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
s://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
Expand All @@ -24,11 +24,8 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel
.env
.nev

# typescript
*.tsbuildinfo
Expand Down
1 change: 1 addition & 0 deletions .nev
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DB_PASSWORD=donote
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node-linker=hoisted
33 changes: 33 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
# Order rate limiting middleware before the reverse proxy
order rate_limit before reverse_proxy
}

:80 {
# Apply security headers
header {
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "geolocation=(), microphone=(), camera=()"
X-XSS-Protection "1; mode=block"
}

# Restrict request body sizes to 2 Megabytes
request_body {
max_size 2mb
}

# Enforce Rate Limiting: Max 10 req/sec per client IP with a burst allowance of 15
rate_limit {
zone api_limit {
key {client_ip}
events 10
window 1s
burst 15
}
}

# Forward all traffic to the api container on port 3000
reverse_proxy api:3000
}
43 changes: 43 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ==========================================
# Stage 1: Core Setup (Official Base)
# ==========================================
FROM node:22-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME/bin:$PATH"
RUN npm install -g corepack@latest && corepack enable

# ==========================================
# Stage 2: Monorepo Build & Bundle Deployment
# ==========================================
FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app

# 1. Install all dependencies across the workspace using BuildKit caching

RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

# 2. Run your tsup compilation script
RUN pnpm api:build

# 3. Magic Step: Let pnpm safely extract your production API bundle
# This isolates your compiled code and production node_modules into a flat /prod/api directory
RUN pnpm deploy --filter=api --prod /prod/api

# 4. Copy Drizzle migrations into the production bundle
COPY apps/api/drizzle /prod/api/drizzle

# ==========================================
# Stage 3: Clean Production Runner
# ==========================================
FROM base AS runner
WORKDIR /prod/api
ENV NODE_ENV=production

# Copy the completely flattened, self-contained production bundle from Stage 2
COPY --from=build /prod/api /prod/api

EXPOSE 3000

# Start your app directly using the package.json start script
CMD [ "pnpm", "start" ]
44 changes: 28 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
# Do Note
A Google Keep like note taking app with some rich text formating options inspired from Notion and some more improvements, will be integrated to a sticky note browser extension (a work in progress 😅).
````mermaid
flowchart TD

---
client[Client / Next.js]
api[Go API]
pg[(Postgres)]

## Main Features that have to be implemented
rp[(Redpanda)]

- [X] Paste image from clipboard, positioned within the text
- [ ] Voice to text
- [ ] Works offline
curator[Feed Poller Worker]
parser[Feed Parser Worker]
analytics[Analytics Worker]

---
rss[External RSS Feeds]

## To run the app
1. Install all the dependencies with: `pnpm install`.
2. Create a supabase project and add the following config variables in a .env file:
```env
NEXT_PUBLIC_SUPABASE_URL = {supabase_url}
NEXT_PUBLIC_SUPABASE_ANON_KEY = {secret_key}
```
4. Run the project in development with: `pnpm run dev`.
client <-->|CRUD + publish| api

api <--> pg

api -->|PostPublished event| rp

curator -->|poll feeds| rss
curator -->|FeedFetched event| rp

rp --> parser
parser -->|store normalized content| pg

client -->|view article| api
api -->|ArticleViewed event| rp

rp --> analytics
analytics -->|aggregate stats| pg ```
````
10 changes: 10 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Server config
PORT=8080
HOST=0.0.0.0

# Database config
# To run in Mock Mode (in-memory array, no Postgres required), set MOCK_DB=true
MOCK_DB=true

# When MOCK_DB is false/unset, DATABASE_URL must be a valid Postgres connection string
DATABASE_URL=postgres://postgres:postgres@localhost:5432/donote
49 changes: 49 additions & 0 deletions apps/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build
/dist

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# files generated by next-pwa
**/public/precache.*.*.js
**/public/sw.js
**/public/workbox-*.js
**/public/worker-*.js
**/public/fallback-*.js
**/public/precache.*.*.js.map
**/public/sw.js.map
**/public/workbox-*.js.map
**/public/worker-*.js.map
**/public/fallback-*.js
Empty file added apps/api/.npmignore
Empty file.
13 changes: 13 additions & 0 deletions apps/api/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from "drizzle-kit";
import * as dotenv from "dotenv";

dotenv.config();

export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/donote",
},
});
29 changes: 29 additions & 0 deletions apps/api/drizzle/0000_stale_nico_minoru.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
CREATE TYPE "public"."user_role" AS ENUM('super_user', 'admin', 'user');--> statement-breakpoint
CREATE TABLE "notes" (
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "notes_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
"creator" uuid NOT NULL,
"note_title" varchar(255) NOT NULL,
"note_content" text DEFAULT '',
"color" varchar(7) DEFAULT '#ffffff',
"is_article" boolean DEFAULT false,
"is_active" boolean DEFAULT true,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"email" varchar(255) NOT NULL,
"password_hash" varchar(255) NOT NULL,
"role" "user_role" DEFAULT 'user' NOT NULL,
"max_notes" integer DEFAULT 5 NOT NULL,
"max_storage_bytes" bigint DEFAULT 1048576 NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "notes" ADD CONSTRAINT "notes_creator_users_id_fk" FOREIGN KEY ("creator") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_notes_creator" ON "notes" USING btree ("creator");--> statement-breakpoint
CREATE INDEX "idx_notes_creator_created" ON "notes" USING btree ("creator","created_at" DESC NULLS LAST);--> statement-breakpoint
CREATE UNIQUE INDEX "idx_users_email" ON "users" USING btree ("email");
2 changes: 2 additions & 0 deletions apps/api/drizzle/0001_small_susan_delgado.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE "notes" ALTER COLUMN "color" SET DATA TYPE varchar(255);--> statement-breakpoint
ALTER TABLE "notes" ALTER COLUMN "color" SET DEFAULT '#ffffff';
2 changes: 2 additions & 0 deletions apps/api/drizzle/0002_rare_starhawk.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE "notes" ADD COLUMN "is_deleted" boolean DEFAULT false;--> statement-breakpoint
ALTER TABLE "notes" DROP COLUMN "is_active";
Loading