Skip to content
Closed
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
45 changes: 45 additions & 0 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Implementation Plan - North Pole Wishlist

## Phase 0: Git Setup & Initialization
- [x] Check if the current directory is an initialized git repository.
- [x] Create and checkout a new feature branch named `north-pole-wishlist`.
- [x] Create the project directory structure (`north_pole_wishlist/`, `app/`, `app/static`, `app/templates`, etc.) as defined in the Tech Spec.
- [x] Initialize a virtual environment (`venv`) and create a `requirements.txt` file with dependencies (`Flask`, `SQLAlchemy`, `Flask-SQLAlchemy`).

## Phase 1: Backend Core (Database & Models)
- [x] Create `config.py` with database URI (SQLite) and secret key configuration.
- [x] Implement `app/models.py` defining `GiftIdea`, `Vote`, and `Comment` classes using strict SQLAlchemy 2.0 style (`Mapped`, `mapped_column`).
- [x] Implement `app/__init__.py` to set up the Flask app factory and initialize the database extension.
- [x] Create a script or CLI command to initialize the database tables (`db.create_all()`).

## Phase 2: Frontend Base & Assets
- [x] Create `app/templates/base.html` with Bootstrap 5 CDN links and the festive color palette (CSS variables for `#D42426`, `#165B33`, etc.).
- [x] Create `app/static/css/style.css` to implement the "Christmas Aesthetic" (fonts, background colors).
- [x] Generate the Hero Image ("Santa Claus flying on his sleigh") using Nano Banana and save to `app/static/img/hero.png`.
- [x] Generate festive icons (Snowflakes, Gift Tags) using Nano Banana or find suitable Bootstrap Icons/FontAwesome replacements.

## Phase 3: Gift Management (Submit & View)
- [x] Implement the `POST /submit` route in `app/routes.py` to handle form submissions and save new `GiftIdea` records.
- [x] Create `app/templates/submit.html` with a form for Title, Description, and Category (dropdown).
- [x] Implement the `GET /` route in `app/routes.py` to fetch all gift ideas, supporting sorting (Ranking/Recency) and category filtering.
- [x] Create `app/templates/index.html` to display the gift feed in a grid or list view, showing title, description, and stats.

## Phase 4: Interaction Features (Voting & Comments)
- [x] Implement the `GET /gift/<id>` route to show gift details, comments, and the voting interface.
- [x] Create `app/templates/detail.html` extending `base.html` to render the single gift view.
- [x] Implement `POST /gift/<id>/vote` logic to calculate and save 1-5 snowflake ratings.
- [x] Implement `POST /gift/<id>/comment` logic to save user comments.
- [x] Update the `GiftIdea` model or queries to efficiently calculate average score and vote counts for ranking.

## Phase 5: Polishing & Testing
- [x] Verify all routes and forms work as expected (Validation checks).
- [x] ensure the ranking algorithm (Average Score > Vote Count) works correctly on the Home page.
- [x] Refine CSS to ensure the "Christmas Aesthetic" is consistent and responsive.

## Phase 6: Completion & Version Control
- [ ] Verify application functionality (Walkthrough of all features).
- [ ] Create a `README.md` file in the project root explaining the application, setup instructions, and architecture.
- [ ] Add all changes to the repository (`git add .`).
- [ ] Commit the changes (`git commit -m "Complete implementation of North Pole Wishlist"`).
- [ ] Push the feature branch to the remote repository.
- [ ] Open a pull request for the feature branch using the Gemini CLI github MCP server.
94 changes: 0 additions & 94 deletions README.md

This file was deleted.

6 changes: 6 additions & 0 deletions north_pole_wishlist/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
venv/
__pycache__/
*.pyc
instance/
.pytest_cache/
app.db
78 changes: 78 additions & 0 deletions north_pole_wishlist/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# North Pole Wishlist 🎅

Welcome to the **North Pole Wishlist**, a community-driven platform where elves, reindeer, and humans alike can discover, share, and curate the best gift ideas for the holiday season!

## Features

- **Gift Idea Management**: Submit your unique gift suggestions with categories like "For Kids", "Tech & Gadgets", and "Stocking Stuffers".
- **Naughty or Nice Voting**: Rate gift ideas on a scale of 1 to 5 snowflakes.
- **Community Ranking**: See what's trending on the "Nice List" based on real-time votes.
- **Discussion Board**: Leave comments and reviews on specific items.
- **Festive Atmosphere**: Immerse yourself in the holiday spirit with a custom Christmas-themed UI.

## Tech Stack

- **Backend**: Python 3, Flask, SQLAlchemy 2.0 (SQLite)
- **Frontend**: HTML5, Bootstrap 5, Custom CSS
- **Assets**: AI-Generated Hero Image & Icons

## Installation & Setup

1. **Clone the repository**:
```bash
git clone <repository-url>
cd north-pole-wishlist
```

2. **Create a virtual environment**:
```bash
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```

3. **Install dependencies**:
```bash
pip install -r requirements.txt
```

4. **Initialize the Database**:
```bash
python init_db.py
```

5. **Run the Application**:
```bash
python run.py
```
(Note: You might need to set `FLASK_APP=north_pole_wishlist/app` or similar depending on how you run it, or just `flask run` from the app dir).

*Actually, use the provided `run.py` if available or `flask run`:*
```bash
flask --app app run --debug
```

## Project Structure

```text
north_pole_wishlist/
├── app/
│ ├── __init__.py # App Factory
│ ├── models.py # Database Models
│ ├── routes.py # Route Handlers
│ ├── static/ # CSS & Images
│ └── templates/ # HTML Templates
├── config.py # Configuration
├── init_db.py # DB Initialization Script
├── requirements.txt # Python Dependencies
└── tests.py # Unit Tests
```

## Contributing

1. Fork the repository.
2. Create your feature branch (`git checkout -b feature/AmazingGift`).
3. Commit your changes (`git commit -m 'Add some AmazingGift'`).
4. Push to the branch (`git push origin feature/AmazingGift`).
5. Open a Pull Request.

Happy Holidays! 🎄
20 changes: 20 additions & 0 deletions north_pole_wishlist/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from flask import Flask
from config import Config
from sqlalchemy.orm import DeclarativeBase
from flask_sqlalchemy import SQLAlchemy

class Base(DeclarativeBase):
pass

db = SQLAlchemy(model_class=Base)

def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)

db.init_app(app)

from app import routes, models
app.register_blueprint(routes.bp)

return app
35 changes: 35 additions & 0 deletions north_pole_wishlist/app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import datetime
from typing import List
from sqlalchemy import String, ForeignKey, Integer, Text, DateTime
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.sql import func
from app import db

class GiftIdea(db.Model):
__tablename__ = 'gift_idea'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(100), nullable=False)
description: Mapped[str] = mapped_column(String(500), nullable=False)
category: Mapped[str] = mapped_column(String(50), nullable=False)
created_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

votes: Mapped[List["Vote"]] = relationship(back_populates="gift")
comments: Mapped[List["Comment"]] = relationship(back_populates="gift")

class Vote(db.Model):
__tablename__ = 'vote'
id: Mapped[int] = mapped_column(primary_key=True)
gift_id: Mapped[int] = mapped_column(ForeignKey('gift_idea.id'))
score: Mapped[int] = mapped_column(Integer, nullable=False) # 1-5

gift: Mapped["GiftIdea"] = relationship(back_populates="votes")

class Comment(db.Model):
__tablename__ = 'comment'
id: Mapped[int] = mapped_column(primary_key=True)
gift_id: Mapped[int] = mapped_column(ForeignKey('gift_idea.id'))
user_name: Mapped[str] = mapped_column(String(100), default="Anonymous Elf")
content: Mapped[str] = mapped_column(Text, nullable=False)
created_at: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

gift: Mapped["GiftIdea"] = relationship(back_populates="comments")
110 changes: 110 additions & 0 deletions north_pole_wishlist/app/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash
from app import db
from app.models import GiftIdea, Vote, Comment
import sqlalchemy as sa
from sqlalchemy import func

bp = Blueprint('main', __name__)

@bp.route('/')
def index():
sort_by = request.args.get('sort', 'ranking')
category = request.args.get('category')

stmt = sa.select(
GiftIdea,
func.coalesce(func.avg(Vote.score), 0).label('avg_score'),
func.count(Vote.id).label('vote_count')
).outerjoin(Vote).group_by(GiftIdea.id)

if category:
stmt = stmt.where(GiftIdea.category == category)

if sort_by == 'recency':
stmt = stmt.order_by(GiftIdea.created_at.desc())
else:
stmt = stmt.order_by(func.coalesce(func.avg(Vote.score), 0).desc(), func.count(Vote.id).desc())

results = db.session.execute(stmt).all()

gifts = []
for row in results:
gift = row[0]
gift.avg_score = row[1]
gift.vote_count = row[2]
gifts.append(gift)

return render_template('index.html', gifts=gifts)

@bp.route('/submit', methods=['GET', 'POST'])
def submit_gift():
if request.method == 'POST':
title = request.form.get('title')
category = request.form.get('category')
description = request.form.get('description')

if not title or not category or not description:
flash('All fields are required!')
return redirect(url_for('main.submit_gift'))

new_gift = GiftIdea(title=title, category=category, description=description)
db.session.add(new_gift)
db.session.commit()

flash('Gift idea submitted successfully! Santa is pleased.')
return redirect(url_for('main.index'))

return render_template('submit.html')

@bp.route('/gift/<int:id>')
def gift_detail(id):
gift = db.session.get(GiftIdea, id)
if not gift:
flash('Gift not found!')
return redirect(url_for('main.index'))

# Calculate aggregate stats for this single gift
# We could do this in Python since we have the relationship loaded,
# but SQL is more efficient for aggregates usually.
# However, since we need the gift object anyway, utilizing the relationship for comments is fine.
# For votes, let's just run a quick query or use the relationship if the list isn't massive.
# Given the scale, iterating relationship is fine, but let's stick to SQL for stats to be consistent.

stats_stmt = sa.select(
func.coalesce(func.avg(Vote.score), 0).label('avg_score'),
func.count(Vote.id).label('vote_count')
).where(Vote.gift_id == id)

stats = db.session.execute(stats_stmt).first()
avg_score = stats[0]
vote_count = stats[1]

return render_template('detail.html', gift=gift, avg_score=avg_score, vote_count=vote_count)

@bp.route('/gift/<int:id>/vote', methods=['POST'])
def vote_gift(id):
score = request.form.get('score')
if score and score.isdigit() and 1 <= int(score) <= 5:
new_vote = Vote(gift_id=id, score=int(score))
db.session.add(new_vote)
db.session.commit()
flash('Vote cast! You are on the Nice List.')
else:
flash('Invalid vote!')

return redirect(url_for('main.gift_detail', id=id))

@bp.route('/gift/<int:id>/comment', methods=['POST'])
def add_comment(id):
content = request.form.get('content')
user_name = request.form.get('user_name') or "Anonymous Elf"

if content and len(content) >= 10:
new_comment = Comment(gift_id=id, content=content, user_name=user_name)
db.session.add(new_comment)
db.session.commit()
flash('Comment posted!')
else:
flash('Comment must be at least 10 characters long.')

return redirect(url_for('main.gift_detail', id=id))
Loading