{"id":51129972,"url":"https://github.com/osisdie/newslens","last_synced_at":"2026-06-25T11:01:33.140Z","repository":{"id":357388573,"uuid":"1236670004","full_name":"osisdie/newslens","owner":"osisdie","description":"NewsLens — AI-powered news aggregator with iOS + Web clients, Stripe subscriptions, and per-source keyword filtering","archived":false,"fork":false,"pushed_at":"2026-05-12T14:22:43.000Z","size":8918,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T16:22:24.476Z","etag":null,"topics":["aggregator","ai","express","jwt","monorepo","news","nodejs","postgres","react","react-native","stripe","vite"],"latest_commit_sha":null,"homepage":"https://github.com/osisdie/newslens","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osisdie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-12T13:13:03.000Z","updated_at":"2026-05-12T14:48:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/osisdie/newslens","commit_stats":null,"previous_names":["osisdie/newslens"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/osisdie/newslens","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osisdie%2Fnewslens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osisdie%2Fnewslens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osisdie%2Fnewslens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osisdie%2Fnewslens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osisdie","download_url":"https://codeload.github.com/osisdie/newslens/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osisdie%2Fnewslens/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34771664,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aggregator","ai","express","jwt","monorepo","news","nodejs","postgres","react","react-native","stripe","vite"],"created_at":"2026-06-25T11:01:31.354Z","updated_at":"2026-06-25T11:01:33.135Z","avatar_url":"https://github.com/osisdie.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NewsLens — AI-Powered News, Filtered Sharply\n\n![CI](https://github.com/osisdie/newslens/actions/workflows/ci.yml/badge.svg)\n![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)\n![Node](https://img.shields.io/badge/node-20-brightgreen.svg)\n![Stripe](https://img.shields.io/badge/Stripe-Subscriptions-635BFF.svg)\n\nNewsLens is a news aggregation app that tracks news from multiple sources based on per-user keywords, then scores every article for fake-news / clickbait / phishing signals. iOS + Web clients backed by a Node/Postgres API with Stripe-powered monthly subscriptions.\n\n## Features\n\n### Core\n- **Multi-source aggregation** — bring your own news sources (Google News, UDN, Yahoo, etc.) with per-source keyword filters.\n- **AI scoring** — each article is tagged with three signals:\n  - Fake-news rate\n  - Clickbait rate\n  - Phishing rate\n- **Latest-first feed** — sorted by publication date with source/keyword facets.\n\n### Subscription tiers\n- **Free** — 3 sources, 3 keywords per source.\n- **Paid (USD $9.99 / month)** — unlimited sources and keywords.\n\n### Billing\n- Stripe Checkout for card capture (test mode supports Sandboxes).\n- Graceful cancel (`cancel_at_period_end`) — keeps access until period end, UI flips from `Renews on` to `Expires on`.\n- Webhook-driven state sync (`checkout.session.completed`, `invoice.payment_succeeded`, `customer.subscription.deleted`).\n\n### Authentication\n- JWT-based session with `/api/auth/register`, `/api/auth/login`, `/api/auth/me`.\n\n## Screenshots\n\n| Step | View |\n|---|---|\n| 1. Auth / Profile | ![](docs/screenshots/01_user_profile.png) |\n| 2. Billing — free tier | ![](docs/screenshots/02_billing_subscription.png) |\n| 3. Stripe-hosted checkout | ![](docs/screenshots/03_stripe_checkout.png) |\n| 4. Success redirect | ![](docs/screenshots/04_stripe_checkout_completed.png) |\n| 5. Active — \"Renews on\" | ![](docs/screenshots/05_billing_subscribed.png) |\n| 6. Cancelled — \"Expires on\" | ![](docs/screenshots/06_billing_cancel_subscription.png) |\n\n## Tech Stack\n\n### Frontend\n- React (Web) + Vite\n- React Native + Expo (iOS)\n- React Router, TanStack Query\n- AsyncStorage on iOS, `localStorage` on Web (JWT)\n\n### Backend\n- Node.js 20 + Express\n- PostgreSQL (with optional SQLite for dev)\n- JWT (`jsonwebtoken`)\n- Puppeteer + Cheerio for scraping\n- Stripe Node SDK\n\n### Infrastructure\n- Stripe Sandboxes for test-mode billing\n- Per-user daily / monthly quota enforcement to bound hosting cost\n- Multi-push `git origin` (GitLab + GitHub mirror)\n\n## Project Structure\n\n```\nnewslens/\n├── mobile/                 # React Native / Expo\n├── web/                    # React + Vite\n├── backend/                # Express API\n├── docs/                   # Setup guides + screenshots\n├── scripts/                # Dev helpers (stripe CLI, slack_notify, test_subscription_flow.sh)\n└── .github/, .gitlab-ci.yml, .pre-commit-config.yaml\n```\n\n## Getting Started\n\n### Prerequisites\n- **Node.js 20** (see [docs/SETUP_NODE.md](./docs/SETUP_NODE.md))\n- **PostgreSQL** (`bash backend/scripts/install-postgresql-wsl.sh` for WSL)\n- Xcode + EAS CLI (iOS only)\n\n### Installation\n\n```bash\n# 1. Clone\ngit clone git@github.com:osisdie/newslens.git\ncd newslens\n\n# 2. Node 20 (if not default)\nnpm run setup-node\nnpm run setup-nvm\n\n# 3. Install per workspace (separate due to React version differences)\ncd backend  \u0026\u0026 npm install \u0026\u0026 cd ..\ncd web      \u0026\u0026 npm install \u0026\u0026 cd ..\ncd mobile   \u0026\u0026 npm install --legacy-peer-deps \u0026\u0026 cd ..\n\n# 4. Configure env\ncp .env.example .env\n# edit .env with your Stripe sandbox keys, JWT secret, etc.\n\n# 5. Initialize DB\ncd backend \u0026\u0026 npm run create-db \u0026\u0026 cd ..\n\n# 6. Start backend + web (separate terminals)\nnpm run dev:backend\nnpm run dev:web\n\n# 7. iOS (separate terminal)\ncd mobile \u0026\u0026 npm run ios\n```\n\n### Pre-commit hooks (recommended)\n\nCatches secrets before they leave your machine:\n\n```bash\npip install pre-commit\npre-commit install\n# Test it:\npre-commit run --all-files\n```\n\n## Environment Variables\n\nAll env vars live in **one place**: the root `./.env` (gitignored). Copy from [`.env.example`](./.env.example) and edit.\n\n### Required\n- `DATABASE_URL` — Postgres connection string\n- `JWT_SECRET` — generate with `openssl rand -base64 32`\n- `STRIPE_SECRET_KEY` — from Stripe Dashboard or Sandbox\n- `STRIPE_WEBHOOK_SECRET` — from `stripe listen` output (local) or webhook endpoint config (prod)\n- `SUBSCRIPTION_PRICE_ID` — Stripe price ID for the monthly plan\n\n### Optional (with defaults)\n- `PORT` (3000), `NODE_ENV` (development)\n- Quota knobs: `DAILY_API_LIMIT`, `MONTHLY_API_LIMIT`, `DAILY_SCRAPE_LIMIT`, `MONTHLY_SCRAPE_LIMIT`\n\n### Client-side\n- `VITE_API_URL` (web, defaults to `/api` via Vite proxy)\n- `EXPO_PUBLIC_API_URL` (mobile, defaults to `http://localhost:3000/api`)\n\nFor Stripe setup, see [docs/STRIPE_SETUP.md](./docs/STRIPE_SETUP.md).\n\n## Subscription Test Flow\n\nEnd-to-end script that walks `free → active → cancel-auto-renew → immediate cancel`:\n\n```bash\n./scripts/test_subscription_flow.sh\n# Follow the printed checkout URL, pay with test card 4242 4242 4242 4242\n```\n\nSee script for assertions and DB queries you can use during manual debugging.\n\n## iOS App Release\n\nSee [docs/IOS_RELEASE_GUIDE.md](./docs/IOS_RELEASE_GUIDE.md) for TestFlight + App Store walkthrough.\n\n```bash\nnpm install -g eas-cli\ncd mobile \u0026\u0026 eas build:configure\neas build --platform ios --profile production\neas submit --platform ios\n```\n\n## CI / Security\n\n- **GitHub Actions** (`.github/workflows/ci.yml`) — gitleaks scan + `npm ci` matrix (backend syntax-check, web `vite build`)\n- **GitLab CI** (`.gitlab-ci.yml`) — same jobs for the GitLab mirror\n- **Pre-commit** (`.pre-commit-config.yaml`) — gitleaks + hygiene hooks block secret leaks at commit time\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosisdie%2Fnewslens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosisdie%2Fnewslens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosisdie%2Fnewslens/lists"}