Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .cursor/environment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"install": "python3 -m pip install --break-system-packages -r requirements.txt && python3 -m pip install --break-system-packages playwright beautifulsoup4 lxml fastapi uvicorn && python3 -m playwright install chromium"
}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__pycache__/
*.py[cod]
.pytest_cache/
.venv/
venv/
data/reports.db
playwright-log.txt
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,82 @@
# Bite-Radar---CastIQ
# Bite-Radar---CastIQ

Minnesota Fishing Reports Aggregator MVP.

Implemented phases:
- Phase 1: Python scraper engine (Mille Lacs source)
- Phase 2: FastAPI backend endpoint (`/api/reports`)
- Phase 3: Next.js + Tailwind frontend feed with source filtering

## Project structure

```text
.
├── .cursor/
│ └── environment.json
├── data/
├── frontend/
├── requirements.txt
└── scraper_engine/
├── api.py
├── database.py
├── main.py
├── models.py
└── scrapers/
└── mille_lacs.py
```

## Setup (backend)

Run from repository root:

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
python -m playwright install chromium
```

## Phase 1: scraper

```bash
python -m scraper_engine.main
```

## Phase 2: API

```bash
python -m uvicorn scraper_engine.api:app --host 127.0.0.1 --port 8000 --reload
```

Test API directly:

```bash
curl "http://127.0.0.1:8000/api/reports"
```

## Phase 3: frontend

In a second terminal:

```bash
cd frontend
npm install
npm run dev
```

Open:

```text
http://127.0.0.1:3000
```

The frontend calls its own Next.js route (`/api/reports`), which proxies to the
Python backend (`http://127.0.0.1:8000/api/reports` by default).

To point the frontend proxy at a different backend URL:

```bash
cd frontend
BACKEND_API_URL="http://127.0.0.1:8000" npm run dev
```
41 changes: 41 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

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

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
55 changes: 55 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Frontend (Phase 3)

This Next.js + Tailwind app renders the unified fishing report feed and supports
source filtering.

## Features implemented

- Fetches reports from `/api/reports` (a Next.js route that proxies to FastAPI)
- Card-based feed UI with:
- Source
- Title
- Timestamp
- 150-character preview
- "Read Full Report" link
- Source filter dropdown (`All` + available source names)
- Responsive layout:
- 1 column on mobile
- 2 columns on medium screens
- 3 columns on large screens

## Run locally

From repository root, start backend first:

```bash
python3 -m uvicorn scraper_engine.api:app --host 127.0.0.1 --port 8000
```

In a second terminal:

```bash
cd frontend
npm install
npm run dev
```

Open:

```text
http://127.0.0.1:3000
```

## Environment variable (optional)

By default, the frontend proxy route calls:

```text
http://127.0.0.1:8000/api/reports
```

To change it, set:

```text
BACKEND_API_URL
```
27 changes: 27 additions & 0 deletions frontend/app/api/reports/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NextResponse } from "next/server";

const DEFAULT_BACKEND_URL = "http://127.0.0.1:8000";

export async function GET() {
const backendUrl = process.env.BACKEND_API_URL ?? DEFAULT_BACKEND_URL;
const url = `${backendUrl}/api/reports`;

try {
const response = await fetch(url, { cache: "no-store" });
if (!response.ok) {
return NextResponse.json(
{ error: `Backend request failed: ${response.status}` },
{ status: 502 },
);
}

const reports = await response.json();
return NextResponse.json(reports);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return NextResponse.json(
{ error: `Could not reach backend API at ${backendUrl}. ${message}` },
{ status: 502 },
);
}
}
Binary file added frontend/app/favicon.ico
Binary file not shown.
28 changes: 28 additions & 0 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--background: #f8fafc;
--foreground: #0f172a;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #020617;
--foreground: #e2e8f0;
}
}

body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
}
34 changes: 34 additions & 0 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: "MN Fishing Reports Aggregator",
description: "Unified Minnesota fishing report feed for MVP testing",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
5 changes: 5 additions & 0 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import ReportsClient from "./reports-client";

export default function Home() {
return <ReportsClient />;
}
Loading