diff --git a/.gitignore b/.gitignore index 4b56acf..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,56 +1 @@ -# compiled output -/dist -/node_modules -/build - -# Logs -logs -*.log -npm-debug.log* -pnpm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# OS -.DS_Store - -# Tests -/coverage -/.nyc_output - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# temp directory -.temp -.tmp - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +node_modules diff --git a/README.md b/README.md index df5b87d..d7bb61d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Future Self +# Futureself -I built Future Self as a simple app for writing messages today and receiving them later in my inbox. It uses a NestJS API with a React (Vite) frontend, PostgreSQL for storage, Redis + BullMQ for scheduling and background jobs, and [Brevo](https://www.brevo.com/) for email delivery. +I built Futureself as a simple app for writing messages today and receiving them later in my inbox. It uses a NestJS API with a React (Vite) frontend, PostgreSQL for storage, Redis + BullMQ for scheduling and background jobs, and [Brevo](https://www.brevo.com/) for email delivery. Users can write a message, choose a delivery date, and let the system handle the scheduling and sending automatically. @@ -8,13 +8,14 @@ Users can write a message, choose a delivery date, and let the system handle the Most reminder apps are built around tasks and notifications. I wanted something more personal: a way to leave notes for myself that arrive at the right time. -Sometimes it’s a reflection I want to revisit months later, a message for an important milestone, encouragement before a difficult period, or just context I know I’ll forget with time. Future Self is built around that idea: delayed personal communication instead of productivity tooling. +Sometimes it's a reflection I want to revisit months later, a message for an important milestone, encouragement before a difficult period, or just context I know I'll forget with time. Futureself is built around that idea: delayed personal communication instead of productivity tooling. > **Note** This repository is still in active development ## Prerequisites - **Node.js** 22.x or current LTS +- **pnpm** 9.x (`npm install -g pnpm`) - **PostgreSQL** and **Redis** (local install or your own containers) - **Brevo** API key only if you want real email (optional locally) @@ -22,15 +23,21 @@ Sometimes it’s a reflection I want to revisit months later, a message for an i ### 1. Postgres and Redis -Run both on the hosts/ports you’ll put in `.env.local`. Defaults below assume `127.0.0.1`. +Run both on the hosts/ports you'll put in `.env.local`. Defaults below assume `127.0.0.1`. -### 2. Backend +### 2. Install dependencies + +From the repo root: ```bash -npm install +pnpm install ``` -Create `.env.local` in the repo root. The app loads `.env.local` first, then `.env.prod` (`[app.module.ts](src/app.module.ts)`). Required keys: `[src/commons/interfaces/env.ts](src/commons/interfaces/env.ts)`. +This installs dependencies for both `apps/api` and `apps/web` in one shot. + +### 3. Backend + +Create `.env.local` in `apps/api/`. Required keys: `apps/api/src/commons/interfaces/env.ts`. ```env NODE_ENVIRONMENT=local @@ -51,7 +58,7 @@ POSTGRES_HOST=127.0.0.1 POSTGRES_PORT=5432 POSTGRES_HOST_DOCKER=postgres -EMAIL_SENDER_NAME=Future Self +EMAIL_SENDER_NAME=Futureself EMAIL_SENDER_EMAIL=noreply@example.com BREVO_API_ENDPOINT=https://api.brevo.com/v3 BREVO_API_KEY=your-brevo-api-key @@ -63,21 +70,9 @@ FRONTEND_URL=http://localhost:5173 - `FRONTEND_URL` must match where the Vite app runs (CORS). - Use a strong `APP_KEY` outside local. -```bash -npm run start:dev -``` - -API listens on `APP_PORT` (log: `Server running on …`). +### 4. Frontend -### 3. Frontend - -```bash -cd frontend -npm install -cp .env.example .env -``` - -Set `VITE_API_URL` to your API origin (no trailing slash): +Create `.env` in `apps/web/` (copy from `apps/web/.env.example`): ```env VITE_API_URL=http://localhost:3000 @@ -85,48 +80,37 @@ VITE_API_URL=http://localhost:3000 Must match backend `APP_PORT`. -```bash -npm run dev -``` - -App: [http://localhost:5173](http://localhost:5173) (Vite default in `[frontend/vite.config.ts](frontend/vite.config.ts)`). - ## Local dev -Two terminals: +Run both from the repo root: + +```bash +pnpm dev # starts both api and web concurrently +``` +Or in separate terminals: -| Terminal | Directory | Command | -| -------- | ----------- | ------------------- | -| API | repo root | `npm run start:dev` | -| UI | `frontend/` | `npm run dev` | +| Terminal | Command | +| -------- | --------------- | +| API | `pnpm dev:api` | +| Web | `pnpm dev:web` | +App: [http://localhost:5173](http://localhost:5173) — API on port defined in `APP_PORT`. Register, compose a message, pick a future delivery time. With `DONT_SEND_EMAIL=true`, the queue still runs; email is skipped. ## Scripts -**API** (repo root) - - -| Command | Purpose | -| -------------------- | --------------------- | -| `npm run start:dev` | Dev server with watch | -| `npm run build` | Production build | -| `npm run start:prod` | Run `dist/` | -| `npm run lint` | ESLint | -| `npm run test` | Unit tests | -| `npm run test:e2e` | E2E tests | - - -**Frontend** (`frontend/`) - - -| Command | Purpose | -| ----------------- | ------------------------------ | -| `npm run dev` | Vite dev server | -| `npm run build` | Typecheck + production build | -| `npm run preview` | Serve production build locally | -| `npm run lint` | ESLint | - - +All scripts run from the **repo root** via pnpm: + +| Command | Purpose | +| ----------------- | ------------------------------------ | +| `pnpm dev` | Start both api and web | +| `pnpm dev:api` | API dev server with watch | +| `pnpm dev:web` | Vite dev server | +| `pnpm build` | Production build for both | +| `pnpm build:api` | Production build for api only | +| `pnpm build:web` | Production build for web only | +| `pnpm test` | Run all tests | +| `pnpm lint` | Lint all packages | +| `pnpm format` | Format all packages | \ No newline at end of file diff --git a/apps/api/.gitignore b/apps/api/.gitignore new file mode 100644 index 0000000..4b56acf --- /dev/null +++ b/apps/api/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/.prettierrc b/apps/api/.prettierrc similarity index 100% rename from .prettierrc rename to apps/api/.prettierrc diff --git a/Dockerfile b/apps/api/Dockerfile similarity index 100% rename from Dockerfile rename to apps/api/Dockerfile diff --git a/apps/api/eslint.config.mjs b/apps/api/eslint.config.mjs new file mode 100644 index 0000000..32465cc --- /dev/null +++ b/apps/api/eslint.config.mjs @@ -0,0 +1,35 @@ +// @ts-check +import eslint from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: ['eslint.config.mjs'], + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + ecmaVersion: 5, + sourceType: 'module', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn' + }, + }, +); \ No newline at end of file diff --git a/nest-cli.json b/apps/api/nest-cli.json similarity index 100% rename from nest-cli.json rename to apps/api/nest-cli.json diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..87e6104 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,94 @@ +{ + "name": "@futureself/api", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "deploy": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" + }, + "dependencies": { + "@futureself/shared": "workspace:*", + "@nestjs/axios": "^4.0.1", + "@nestjs/bullmq": "^11.0.4", + "@nestjs/common": "^11.0.1", + "@nestjs/config": "^4.0.3", + "@nestjs/core": "^11.0.1", + "@nestjs/mapped-types": "*", + "@nestjs/platform-express": "^11.0.1", + "@nestjs/throttler": "^6.5.0", + "@nestjs/typeorm": "^11.0.1", + "@redis/client": "^5.12.1", + "axios": "^1.14.0", + "bcrypt": "^6.0.0", + "bullmq": "^5.73.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.15.1", + "dayjs": "^1.11.20", + "handlebars": "^4.7.9", + "helmet": "^8.1.0", + "jsonwebtoken": "^9.0.3", + "morgan": "^1.10.1", + "nanoid": "^5.1.7", + "pg": "^8.20.0", + "redis": "^5.11.0", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1", + "typeorm": "^0.3.28" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.18.0", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", + "@swc/cli": "^0.6.0", + "@swc/core": "^1.10.7", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.7", + "@types/supertest": "^6.0.2", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2", + "globals": "^16.0.0", + "jest": "^29.7.0", + "prettier": "^3.4.2", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript-eslint": "^8.20.0" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/src/app.controller.ts b/apps/api/src/app.controller.ts similarity index 100% rename from src/app.controller.ts rename to apps/api/src/app.controller.ts diff --git a/src/app.module.ts b/apps/api/src/app.module.ts similarity index 100% rename from src/app.module.ts rename to apps/api/src/app.module.ts diff --git a/src/app.service.ts b/apps/api/src/app.service.ts similarity index 100% rename from src/app.service.ts rename to apps/api/src/app.service.ts diff --git a/src/auth/auth-session.service.ts b/apps/api/src/auth/auth-session.service.ts similarity index 100% rename from src/auth/auth-session.service.ts rename to apps/api/src/auth/auth-session.service.ts diff --git a/src/auth/auth.controller.spec.ts b/apps/api/src/auth/auth.controller.spec.ts similarity index 100% rename from src/auth/auth.controller.spec.ts rename to apps/api/src/auth/auth.controller.spec.ts diff --git a/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts similarity index 100% rename from src/auth/auth.controller.ts rename to apps/api/src/auth/auth.controller.ts diff --git a/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts similarity index 100% rename from src/auth/auth.module.ts rename to apps/api/src/auth/auth.module.ts diff --git a/src/auth/auth.service.spec.ts b/apps/api/src/auth/auth.service.spec.ts similarity index 100% rename from src/auth/auth.service.spec.ts rename to apps/api/src/auth/auth.service.spec.ts diff --git a/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts similarity index 100% rename from src/auth/auth.service.ts rename to apps/api/src/auth/auth.service.ts diff --git a/src/auth/dto/forget-password.dto.ts b/apps/api/src/auth/dto/forget-password.dto.ts similarity index 100% rename from src/auth/dto/forget-password.dto.ts rename to apps/api/src/auth/dto/forget-password.dto.ts diff --git a/src/auth/dto/login.dto.ts b/apps/api/src/auth/dto/login.dto.ts similarity index 100% rename from src/auth/dto/login.dto.ts rename to apps/api/src/auth/dto/login.dto.ts diff --git a/src/auth/dto/reset-password.dto.ts b/apps/api/src/auth/dto/reset-password.dto.ts similarity index 100% rename from src/auth/dto/reset-password.dto.ts rename to apps/api/src/auth/dto/reset-password.dto.ts diff --git a/src/auth/entities/auth.entity.ts b/apps/api/src/auth/entities/auth.entity.ts similarity index 100% rename from src/auth/entities/auth.entity.ts rename to apps/api/src/auth/entities/auth.entity.ts diff --git a/src/auth/guards/auth.guard.ts b/apps/api/src/auth/guards/auth.guard.ts similarity index 100% rename from src/auth/guards/auth.guard.ts rename to apps/api/src/auth/guards/auth.guard.ts diff --git a/src/auth/interfaces/auth-session.interface.ts b/apps/api/src/auth/interfaces/auth-session.interface.ts similarity index 100% rename from src/auth/interfaces/auth-session.interface.ts rename to apps/api/src/auth/interfaces/auth-session.interface.ts diff --git a/src/commons/interfaces/env.ts b/apps/api/src/commons/interfaces/env.ts similarity index 100% rename from src/commons/interfaces/env.ts rename to apps/api/src/commons/interfaces/env.ts diff --git a/src/commons/interfaces/req.ts b/apps/api/src/commons/interfaces/req.ts similarity index 100% rename from src/commons/interfaces/req.ts rename to apps/api/src/commons/interfaces/req.ts diff --git a/src/constants/index.ts b/apps/api/src/constants/index.ts similarity index 100% rename from src/constants/index.ts rename to apps/api/src/constants/index.ts diff --git a/src/email/email.service.ts b/apps/api/src/email/email.service.ts similarity index 100% rename from src/email/email.service.ts rename to apps/api/src/email/email.service.ts diff --git a/src/email/interfaces/send-email.interface.ts b/apps/api/src/email/interfaces/send-email.interface.ts similarity index 100% rename from src/email/interfaces/send-email.interface.ts rename to apps/api/src/email/interfaces/send-email.interface.ts diff --git a/src/main.ts b/apps/api/src/main.ts similarity index 100% rename from src/main.ts rename to apps/api/src/main.ts diff --git a/src/messenger/dto/create-messenger.dto.ts b/apps/api/src/messenger/dto/create-messenger.dto.ts similarity index 100% rename from src/messenger/dto/create-messenger.dto.ts rename to apps/api/src/messenger/dto/create-messenger.dto.ts diff --git a/src/messenger/dto/update-messenger.dto.ts b/apps/api/src/messenger/dto/update-messenger.dto.ts similarity index 100% rename from src/messenger/dto/update-messenger.dto.ts rename to apps/api/src/messenger/dto/update-messenger.dto.ts diff --git a/src/messenger/entities/message.entity.ts b/apps/api/src/messenger/entities/message.entity.ts similarity index 100% rename from src/messenger/entities/message.entity.ts rename to apps/api/src/messenger/entities/message.entity.ts diff --git a/src/messenger/message-to-future.consumer.ts b/apps/api/src/messenger/message-to-future.consumer.ts similarity index 100% rename from src/messenger/message-to-future.consumer.ts rename to apps/api/src/messenger/message-to-future.consumer.ts diff --git a/src/messenger/messenger.controller.spec.ts b/apps/api/src/messenger/messenger.controller.spec.ts similarity index 100% rename from src/messenger/messenger.controller.spec.ts rename to apps/api/src/messenger/messenger.controller.spec.ts diff --git a/src/messenger/messenger.controller.ts b/apps/api/src/messenger/messenger.controller.ts similarity index 100% rename from src/messenger/messenger.controller.ts rename to apps/api/src/messenger/messenger.controller.ts diff --git a/src/messenger/messenger.module.ts b/apps/api/src/messenger/messenger.module.ts similarity index 100% rename from src/messenger/messenger.module.ts rename to apps/api/src/messenger/messenger.module.ts diff --git a/src/messenger/messenger.service.spec.ts b/apps/api/src/messenger/messenger.service.spec.ts similarity index 100% rename from src/messenger/messenger.service.spec.ts rename to apps/api/src/messenger/messenger.service.spec.ts diff --git a/src/messenger/messenger.service.ts b/apps/api/src/messenger/messenger.service.ts similarity index 100% rename from src/messenger/messenger.service.ts rename to apps/api/src/messenger/messenger.service.ts diff --git a/src/redis/redis.module.ts b/apps/api/src/redis/redis.module.ts similarity index 100% rename from src/redis/redis.module.ts rename to apps/api/src/redis/redis.module.ts diff --git a/src/redis/redis.service.ts b/apps/api/src/redis/redis.service.ts similarity index 100% rename from src/redis/redis.service.ts rename to apps/api/src/redis/redis.service.ts diff --git a/src/user/dto/create-user.dto.ts b/apps/api/src/user/dto/create-user.dto.ts similarity index 100% rename from src/user/dto/create-user.dto.ts rename to apps/api/src/user/dto/create-user.dto.ts diff --git a/src/user/dto/update-user.dto.ts b/apps/api/src/user/dto/update-user.dto.ts similarity index 100% rename from src/user/dto/update-user.dto.ts rename to apps/api/src/user/dto/update-user.dto.ts diff --git a/src/user/entities/user.entity.ts b/apps/api/src/user/entities/user.entity.ts similarity index 100% rename from src/user/entities/user.entity.ts rename to apps/api/src/user/entities/user.entity.ts diff --git a/src/user/user.controller.spec.ts b/apps/api/src/user/user.controller.spec.ts similarity index 100% rename from src/user/user.controller.spec.ts rename to apps/api/src/user/user.controller.spec.ts diff --git a/src/user/user.controller.ts b/apps/api/src/user/user.controller.ts similarity index 100% rename from src/user/user.controller.ts rename to apps/api/src/user/user.controller.ts diff --git a/src/user/user.module.ts b/apps/api/src/user/user.module.ts similarity index 100% rename from src/user/user.module.ts rename to apps/api/src/user/user.module.ts diff --git a/src/user/user.service.spec.ts b/apps/api/src/user/user.service.spec.ts similarity index 100% rename from src/user/user.service.spec.ts rename to apps/api/src/user/user.service.spec.ts diff --git a/src/user/user.service.ts b/apps/api/src/user/user.service.ts similarity index 100% rename from src/user/user.service.ts rename to apps/api/src/user/user.service.ts diff --git a/src/utils/filters/global-exception.filter.ts b/apps/api/src/utils/filters/global-exception.filter.ts similarity index 100% rename from src/utils/filters/global-exception.filter.ts rename to apps/api/src/utils/filters/global-exception.filter.ts diff --git a/src/utils/http.util.ts b/apps/api/src/utils/http.util.ts similarity index 100% rename from src/utils/http.util.ts rename to apps/api/src/utils/http.util.ts diff --git a/src/utils/interfaces/get_request.interface.ts b/apps/api/src/utils/interfaces/get_request.interface.ts similarity index 100% rename from src/utils/interfaces/get_request.interface.ts rename to apps/api/src/utils/interfaces/get_request.interface.ts diff --git a/src/utils/interfaces/post_request.interface.ts b/apps/api/src/utils/interfaces/post_request.interface.ts similarity index 100% rename from src/utils/interfaces/post_request.interface.ts rename to apps/api/src/utils/interfaces/post_request.interface.ts diff --git a/src/utils/jwt.utils.ts b/apps/api/src/utils/jwt.utils.ts similarity index 100% rename from src/utils/jwt.utils.ts rename to apps/api/src/utils/jwt.utils.ts diff --git a/src/utils/string.utils.ts b/apps/api/src/utils/string.utils.ts similarity index 100% rename from src/utils/string.utils.ts rename to apps/api/src/utils/string.utils.ts diff --git a/src/utils/time.util.spec.ts b/apps/api/src/utils/time.util.spec.ts similarity index 100% rename from src/utils/time.util.spec.ts rename to apps/api/src/utils/time.util.spec.ts diff --git a/src/utils/time.util.ts b/apps/api/src/utils/time.util.ts similarity index 100% rename from src/utils/time.util.ts rename to apps/api/src/utils/time.util.ts diff --git a/src/utils/validators/is-future-time-string.validator.ts b/apps/api/src/utils/validators/is-future-time-string.validator.ts similarity index 100% rename from src/utils/validators/is-future-time-string.validator.ts rename to apps/api/src/utils/validators/is-future-time-string.validator.ts diff --git a/templates/forget_password.hbs b/apps/api/templates/forget_password.hbs similarity index 78% rename from templates/forget_password.hbs rename to apps/api/templates/forget_password.hbs index 342575d..0eb9d29 100644 --- a/templates/forget_password.hbs +++ b/apps/api/templates/forget_password.hbs @@ -1,12 +1,12 @@
-Hello {{name}},
-We received a request to reset your Future Self password.
+We received a request to reset your Futureself password.
diff --git a/templates/message_to_future.hbs b/apps/api/templates/message_to_future.hbs similarity index 100% rename from templates/message_to_future.hbs rename to apps/api/templates/message_to_future.hbs diff --git a/templates/signin_magic_link.hbs b/apps/api/templates/signin_magic_link.hbs similarity index 97% rename from templates/signin_magic_link.hbs rename to apps/api/templates/signin_magic_link.hbs index e12c216..1c79627 100644 --- a/templates/signin_magic_link.hbs +++ b/apps/api/templates/signin_magic_link.hbs @@ -2,7 +2,7 @@ -