Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
621bee1
Define Collection Archive product
Hgaines88 Aug 13, 2026
c04cbac
Add version one user stories
Hgaines88 Aug 13, 2026
10b0236
Define designer and collection data model
Hgaines88 Aug 13, 2026
ffc0897
Create designers table
Hgaines88 Aug 13, 2026
99f4249
Add collections table and designer relationship
Hgaines88 Aug 13, 2026
32b28cf
Add sample archive data
Hgaines88 Aug 13, 2026
521c256
Add database initialization script
Hgaines88 Aug 13, 2026
5d7c3d7
Read designers from SQLite with Python
Hgaines88 Aug 13, 2026
2657af8
Extract reusable database connection
Hgaines88 Aug 13, 2026
8e443ca
Create minimal FastAPI application
Hgaines88 Aug 13, 2026
256754a
Add designer list endpoint
Hgaines88 Aug 13, 2026
7b12eae
Add designer list endpoint
Hgaines88 Aug 13, 2026
bb042e3
List collections for a designer
Hgaines88 Aug 14, 2026
8f6b86e
Add collection detail endpoint
Hgaines88 Aug 14, 2026
0279f37
Create designers with validated API input
Hgaines88 Aug 14, 2026
e8680a8
Add designer update endpoint
Hgaines88 Aug 14, 2026
d63695f
Add designer deletion endpoint
Hgaines88 Aug 14, 2026
ad53eb3
Define validated collection input
Hgaines88 Aug 14, 2026
7a1ba47
Add collection creation endpoint
Hgaines88 Aug 14, 2026
f38bac2
Add collection update and delete endpoints
Hgaines88 Aug 14, 2026
1c7828c
Add complete collection list endpoint
Hgaines88 Aug 14, 2026
213d9e7
Document Python runtime dependencies
Hgaines88 Aug 14, 2026
58919fc
Remove unreachable designer list code
Hgaines88 Aug 14, 2026
ab12474
Add initial API tests
Hgaines88 Aug 14, 2026
8dcc596
Test cascading designer deletion
Hgaines88 Aug 14, 2026
c66df28
Add Collection Archive home page
Hgaines88 Aug 14, 2026
742c6ae
Load designers on archive home page
Hgaines88 Aug 14, 2026
9527fad
Add designer profile pages
Hgaines88 Aug 14, 2026
76c68f6
Add collection detail page
Hgaines88 Aug 14, 2026
355ad25
Add shared archive styling
Hgaines88 Aug 14, 2026
52700e5
Add designer creation form
Hgaines88 Aug 14, 2026
f84b74b
Add collection creation form
Hgaines88 Aug 14, 2026
d7a2ce2
Add collection deletion control
Hgaines88 Aug 14, 2026
04901cb
Add collection editing interface
Hgaines88 Aug 14, 2026
99ad9e4
Add designer editing and website links
Hgaines88 Aug 14, 2026
b664df7
Add designer deletion control
Hgaines88 Aug 14, 2026
77db3d1
Document Collection Archive architecture and setup
Hgaines88 Aug 14, 2026
7c900cc
Expand and correct archive seed data
Hgaines88 Aug 14, 2026
219523d
Make database initialization non-destructive
Hgaines88 Aug 14, 2026
69ed546
Replace Hussein Chalayan with Junya Watanabe
Hgaines88 Aug 14, 2026
1aa956c
Expand and audit collection seed data
Hgaines88 Aug 14, 2026
c4c77ba
Add Anonymous Club and Telfar seed records
Hgaines88 Aug 14, 2026
0238ead
Add Rick Owens seed record
Hgaines88 Aug 15, 2026
a81b200
Add React UI and database migrations
Hgaines88 Aug 15, 2026
c489923
Add focused database migration tests
Hgaines88 Aug 15, 2026
b3e5ac0
Add curated collection media and designer flags
Hgaines88 Aug 15, 2026
6171cde
Clarify Collection Archive overview
Hgaines88 Aug 15, 2026
e8a7cb9
Document multi-designer credits as future feature
Hgaines88 Aug 15, 2026
66c29d7
Potential fix for pull request finding
Hgaines88 Aug 15, 2026
5c29721
Address Copilot review feedback
Hgaines88 Aug 15, 2026
97ee187
Expand archive and redesign interfaces
Hgaines88 Aug 16, 2026
238e9ef
Preserve archive data and demo evidence
Hgaines88 Aug 16, 2026
dc75a20
Merge pull request #37 from Hgaines88/agent/preserve-archive-data
Hgaines88 Aug 16, 2026
f941204
Polish archive data, API, and clients
Hgaines88 Aug 16, 2026
5a03fb4
Expand designer collection archive
Hgaines88 Aug 16, 2026
92b61d7
docs: define Collection Archive v0.2.0
Hgaines88 Aug 16, 2026
dd88145
docs: standardize version references and add signature
Hgaines88 Aug 16, 2026
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.venv/
__pycache__/
*.py[cod]
.pytest_cache/
data/*.db
.DS_Store
56 changes: 56 additions & 0 deletions DATA_MODEL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Collection Archive v0.2.0 Data Model

## Designer

Represents an individual fashion designer.

| Field | Meaning | Required? |
|---|---|---|
| id | Internal unique identifier | Yes |
| full_name | Designer’s full name | Yes |
| nationality | Designer’s nationality | No |
| birth_year | Year the designer was born | No |
| website | Designer’s official website | No |
| biography | Background and career information | No |

## Collection

Represents a collection credited to one lead designer.

| Field | Meaning | Required? |
|---|---|---|
| id | Internal unique identifier | Yes |
| designer_id | Identifies the lead designer | Yes |
| label | Label or fashion house that released it | Yes |
| name | Collection’s given name, if it has one | No |
| season | Fashion season, such as Spring/Summer | Yes |
| release_year | Year it was released or planned | Yes |
| status | Concept, in production, released, or archived | Yes |
| piece_count | Number of looks or pieces | No |
| description | Collection notes and context | No |

A collection name is optional because many fashion collections are unnamed or eponymous and are instead identified by label, season, and year.

## Collection media

Represents a curated external resource associated with one collection. The
archive stores links and YouTube video IDs, not copyrighted media files.

| Field | Meaning | Required? |
|---|---|---|
| id | Internal unique identifier | Yes |
| collection_id | Identifies the collection | Yes |
| media_type | Curated source or YouTube video | Yes |
| media_value | Source URL or normalized YouTube video ID | Yes |

## Relationship rules

- One designer may have zero or many collections.
- Every collection must reference one existing designer.
- Deleting a designer deletes their collection records.
- One collection may have a source link and a YouTube video.
- Deleting a collection deletes its media records.

---

*(h)gaines.*
55 changes: 55 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Collection Archive v0.2.0

## Purpose

A public-facing archive that helps people discover which individual designers created collections for different fashion labels throughout their careers.

## v0.2.0 user stories

- As a user, I can view all archived designers so I can discover who created fashion collections.
- As a user, I can view the collections credited to a designer across different labels and seasons.
- As a user, I can open a collection to see its label, season, year, status, piece count, and description.
- As a user, I can follow a curated source or watch an official embedded runway video when available.
- As a user, I can add, edit, and delete designer (& collection) records.
- Before public deployment both user authentication and an audit system must be encoded ensuring only verified users can make archival edits (v1.0.0).

## Home page

For a small archive, the home page can initially show every designer.
Larger archive should show only recent or featured designers with a separate
"View All" page.

## Designer page

Full Name, Country/Nationality, Birth Year, Website. Background/Bio

## Collection page

Each collection page should show basic details about each collection:
Lead Designer
Label/Fashion House
Season
Release Year
Status (archived, released, concept, in-production, etc)
Piece Count
Description
Curated source link
Official YouTube runway video

## v0.2.0 features

CRUD functionality for Designers & Collections by any/all users.

## Future features

What are we deliberately postponing for v1.0.0?
- Postponing Authentication (login) for authorized edits vs everyday users. Every edit: record the author, timestamp, previous value, and reason for the change
- Direct image uploads and image hosting remain postponed. Collection pages can include curated source links and official YouTube embeds.
- Support for multiple credited designers on one collection through a
`collection_designers` junction table. This would extend the v0.2.0
designer-to-collections model for collaborations and co-designer credits.
- Authentication, Role-based permissions, edit history, rollback, and a small moderation queue.

---

*(h)gaines.*
129 changes: 129 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,128 @@
## Collection Archive v0.2.0

A public-facing archive that helps people discover which individual designers created collections for different fashion labels throughout their careers.

### What this product does:

Users can add/edit designer/collection records (think Wiki)
Visitors can view archived collections to discover the designers who created certain fashion collections across different labels and seasons.

### Data relationship

One designer may have many collections across many labels/seasons. (OneToMany) Eventually I think I should include a junction table to represent collections credited to multiple designers. (ManyToMany)

### Technology

- SQLite
- Python
- FastAPI
- Vanilla HTML, CSS, and JavaScript
- React and Vite
- Pytest

### Run locally

```bash
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
python3 -m scripts.init_db
uvicorn app.main:app --reload
```

In a second terminal, start the React client:

```bash
cd react-ui
npm install
npm run dev
```

Vite serves React at `http://localhost:5173` and proxies `/api` requests to
FastAPI at `http://127.0.0.1:8000`. The Vanilla client is served directly by
FastAPI at `http://127.0.0.1:8000`.

### Run the tests

With the virtual environment active, run the complete suite from the repository
root:

```bash
pytest -q
```

To print the designers currently stored in the archive from the project root,
run the utility script as a Python module:

```bash
python3 -m app.list_designers
```

`init_db.py` creates `data/archive.db` from the canonical `data/archive.json`
snapshot only when the database does not already exist. It never overwrites
live archive records.

### Preserve and restore archive content

SQLite is the local runtime database and remains ignored by Git. The canonical,
reviewable content record is `data/archive.json`. After making approved content
changes through either UI, refresh that snapshot with:

```bash
python3 -m scripts.archive_data export
```

Restore it into a new database, or merge it into an existing database, with:

```bash
python3 -m scripts.archive_data import --database data/restored.db --replace
```

Designer and collection keys in the JSON are stable text identifiers; generated
SQLite IDs are deliberately not exported. Tests verify that export → import →
export produces identical content and that repeated merge imports are idempotent.

After initialization, designers and collections added through either web UI
are stored in that same live database and appear in both interfaces. SQL files
under `sql/migrations/` contain deliberate database upgrades. FastAPI applies
each migration once at startup and records it in `schema_migrations`; migrations
never recreate the database from seed data.

### Application Structure

sql/schema.sql
#Schema.sql defines the database tables, fields, constraints, foreign key, and index.
data/archive.json
#The canonical archive content. It is deterministic, human-readable, portable, and committed separately from schema migrations.
sql/seed.sql
#Legacy instructional seed data retained for the original SQL exercise and tests. Migrations 001 and 003–005 are historical data corrections retained for reproducibility; all new curated content goes through data/archive.json rather than new data migrations.
scripts/init_db.py
#Init_db.py restores the canonical JSON archive only when the database does not exist.
app/database.py
#Database.py opens and configures connections used during normal API reads and writes.
app/schemas.py
#Schemas.py defines the accepted structure and validation rules for designer and collection data received by the API. The SQL tables remain defined separately in schema.sql.
app/main.py
#Main.py defines the middle-tier FastAPI application. Uvicorn receives HTTP requests and passes them to matching FastAPI routes. Those routes validate requests, run SQL through a database connection, and return data or errors to the frontend as HTTP responses.
web/
#The web/ directory contains the user-facing layer. HTML defines the structure and content of each page, CSS controls its visual presentation, and JavaScript loads archive data, handles forms, and communicates with the API.
tests/
#The tests verify API functionality by sending predefined input and comparing the response with expected output. Each test uses a temporary database so the real archive data is not changed.
react-ui/src/App.jsx
#App.jsx is the React routing map. It connects browser URLs to page components, places those pages inside a shared layout, and includes routes for listing, viewing, creating, editing, and handling unknown pages.
react-ui/src/pages/DesignerList.jsx
#DesignerList.jsx requests designers from FastAPI when it first loads, stores the result in React state, and maps each designer record into a linked card on the home page.
react-ui/src/pages/DesignerDetail.jsx
#DesignerDetail reads a designer ID from the React route. When the component loads, it requests both the designer record and that designer’s collections from FastAPI. It stores both responses in React state and renders the one-to-many relationship. It also links to the create and edit forms. When a deletion is confirmed, it sends a DELETE request and SQLite performs the cascading collection deletion.
react-ui/src/pages/DesignerForm.jsx
#DesignerForm handles both creating and editing designers. It detects edit mode from the route parameter. Its inputs are controlled by one state object, and a shared change handler updates the relevant property. On submission, it converts form strings into the types expected by FastAPI, changes blank optional fields to null, normalizes the website address, and sends either POST or PUT. After a successful response, it navigates to the saved designer’s profile.
react-ui/src/pages/CollectionDetail.jsx
#CollectionDetail gets the collection ID from the React route and requests that record from FastAPI. The API response includes the collection’s foreign key and the designer name obtained through a SQL join. The component displays optional fields with appropriate fallbacks and links back to the parent designer. It can also navigate to the edit form or delete the collection and return to its designer’s profile.
react-ui/src/pages/CollectionForm.jsx
#CollectionForm handles both collection creation and editing. When creating, it obtains the parent designer ID from the nested URL. When editing, it obtains the designer ID from the existing collection. Its controlled fields are stored in React state, and submission converts the string input values into the integer and null values expected by FastAPI. The payload includes designer_id, which connects the collection to its parent. FastAPI validates the parent, while SQLite enforces the foreign key and uniqueness rules.
react-ui/src/api.js
#Api.js centralizes communication between React and FastAPI. It prefixes API requests so Vite can proxy them to the backend, adds the JSON content header when a request has a body, parses successful JSON responses, handles empty deletion responses, and converts unsuccessful HTTP responses into JavaScript errors that page components can display.

# OnesToManys (ListDetails)

The point of this project is to explore what a 3-tier web application is like.
Expand Down Expand Up @@ -282,3 +407,7 @@ instructor.
- Hospital (master) - Patients (detail)
- Album (master) - Photos (detail)
- Survey (master) - Questions (detail)

---

*(h)gaines.*
60 changes: 60 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import sqlite3
from pathlib import Path


PROJECT_ROOT = Path(__file__).resolve().parent.parent
DATABASE_PATH = PROJECT_ROOT / "data" / "archive.db"
MIGRATIONS_PATH = PROJECT_ROOT / "sql" / "migrations"


def connect() -> sqlite3.Connection:
connection = sqlite3.connect(DATABASE_PATH)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys = ON")
return connection


def apply_migrations() -> list[str]:
"""Apply each pending SQL migration exactly once."""
connection = connect()
applied = []

try:
connection.execute(
"""
CREATE TABLE IF NOT EXISTS schema_migrations (
filename TEXT PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)
"""
)

completed = {
row["filename"]
for row in connection.execute(
"SELECT filename FROM schema_migrations"
).fetchall()
}

for migration_path in sorted(MIGRATIONS_PATH.glob("*.sql")):
if migration_path.name in completed:
continue

migration_sql = migration_path.read_text()
quoted_filename = migration_path.name.replace("'", "''")

connection.executescript(
"BEGIN IMMEDIATE;\n"
f"{migration_sql}\n"
"INSERT INTO schema_migrations (filename) "
f"VALUES ('{quoted_filename}');\n"
"COMMIT;"
)
applied.append(migration_path.name)
except Exception:
connection.rollback()
raise
finally:
connection.close()

return applied
22 changes: 22 additions & 0 deletions app/list_designers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from app.database import connect


connection = connect()

rows = connection.execute(
"""
SELECT
id,
full_name,
nationality,
birth_year
FROM designers
ORDER BY full_name
"""
).fetchall()

for row in rows:
designer = dict(row)
print(f"{designer['full_name']} — {designer['nationality']}")

connection.close()
Loading