CURAS / README.md
stevafernandes's picture
Multi-select areas, colour-coded area tags, Creighton students only, titles removed, Creighton blue
4f5fdec verified
|
Raw History Blame Contribute Delete
7.4 kB
---
title: CURAS Research Database
colorFrom: gray
colorTo: gray
sdk: docker
app_port: 7860
pinned: false
---
# CURAS Research Database
A public database of undergraduate research opportunities posted and updated by the
Center for Undergraduate Research and Scholarship (CURAS). Faculty submit new listings or
updates through a form, a CURAS administrator approves them, and approved listings appear
on the public page immediately.
- **Public page** (`/`): search and filter listings by area of study (several at once),
type of opportunity, term and standing. Listings are headed by the faculty member's name,
every listing states that it is open to Creighton students only, and each area of study
is shown as a colour-coded tag (the colours are defined in `app/fields.py` and
`app/static/style.css`). Each listing has a permanent link (`/listings/<id>`). The pages
use Creighton's brand palette (Pantone 286 C blue, #0054A6).
- **Submission form** (`/submit`): faculty enter their name and NetID plus every listing
field. Choosing an existing listing pre-fills the form to propose an update.
- **Admin** (`/admin`): shared-password sign-in. Review pending submissions (updates show a
side-by-side comparison with the current listing), approve or reject, edit, retire and
restore listings, add a listing directly, export everything as CSV.
Data lives in Supabase (Postgres). The application talks to Supabase only from the server
with the `service_role` key; Row Level Security is enabled with no policies, so the public
`anon` key can neither read nor write the tables.
## One-time setup
### 1. Create the tables in Supabase
In the Supabase dashboard open **SQL Editor**, paste the contents of
[`supabase/schema.sql`](supabase/schema.sql) and run it. It creates two tables,
`listings` and `submissions`, and locks them down.
### 2. Collect the four secrets
| Secret | Where it comes from |
|---|---|
| `SUPABASE_URL` | Supabase dashboard: the **Connect** button at the top of the project, or Settings, Data API. It looks like `https://<ref>.supabase.co` |
| `SUPABASE_SERVICE_ROLE_KEY` | Settings, **API Keys**: create or copy a **Secret** key (starts with `sb_secret_`). On older projects the legacy `service_role` key under the "Legacy API keys" tab also works. Never the publishable or anon key, and never put it in a browser |
| `ADMIN_PASSWORD` | A long password you choose (at least 12 characters). Everyone who approves listings uses it. |
| `SESSION_SECRET` | Any long random string (for example 48 random characters). Signs the admin session cookie. |
### 3. Load the existing listings (once)
In the SQL Editor, paste the contents of [`supabase/seed_listings.sql`](supabase/seed_listings.sql)
and run it. It inserts the 50 listings from the September 2026 workbook as published
listings and ends by reporting `listings_loaded = 50`. It refuses to run a second time, so
it cannot create duplicates.
Nothing needs to be installed or run on your own computer. (If you ever receive a newer
workbook, `scripts/import_excel.py` converts it to `seed/listings_seed.json` and
`scripts/make_seed_sql.py` regenerates the SQL file; both need Python with
`scripts/requirements-dev.txt` installed. `scripts/seed_supabase.py` is an alternative that
loads the JSON through the API using a `.env` file.)
### 4. Deploy on Hugging Face
1. Create a new Space: **Docker** SDK, **CPU basic** hardware, visibility of your choice
(the public page needs to be public for students to see it).
2. Upload these files, keeping the folder structure: `Dockerfile`, `requirements.txt`,
`README.md` and the whole `app/` folder. (`supabase/`, `scripts/`, `seed/` and `tests/`
are not needed on the Space but are harmless if uploaded.)
3. In the Space, open **Settings**, then **Variables and secrets**, and add the four
secrets from step 2 as **Secrets** (not public variables).
4. The Space builds and starts. Check `https://<owner>-<space-name>.hf.space/healthz`;
it should report `"db_configured": true` and `"admin_configured": true`.
5. Share the direct address `https://<owner>-<space-name>.hf.space/` with students and
faculty. The Space page on huggingface.co shows the same site inside a frame, which
works for browsing and submitting but not for admin sign-in (see below).
Optional settings (add as Variables if you need them): `APP_TIMEZONE` (default
`America/Chicago`, the calendar day stamped on "Updated"), `SESSION_HOURS` (default 8),
`CACHE_SECONDS` (default 60, how long the public page may show cached listings).
### 5. Sign in as admin
Open the Space directly at its own address, `https://<owner>-<space-name>.hf.space/admin/login`,
not inside the Space page on huggingface.co. The sign-in cookie is only sent to the
site's own address, so signing in inside the embedded frame does not work (the page says
so and links to the direct address). Sessions last 8 hours. After 5 wrong passwords from
one network the sign-in waits 15 minutes.
To change the admin password, change the `ADMIN_PASSWORD` secret and restart the Space.
Changing `SESSION_SECRET` signs every admin out.
## How approval works
1. A faculty member submits the form. The submission is stored with status `pending` and
is not public.
2. An admin opens **Admin**, reviews it and clicks **Approve and publish** (or rejects it
with an optional note that stays private).
3. On approval a new listing is created, or, for an update, the existing listing is
replaced and its "Updated" date set to today. The public page shows the change
immediately.
Listings are never deleted from the admin pages; **Retire** hides a listing and
**Restore** brings it back. An update approved for a retired listing changes its content
but leaves it retired until an admin restores it.
Listings imported from the workbook that lack one of the required fields (for example a
standing or an area of study) can still be edited and updated without inventing that
content; brand-new listings must fill every required field.
## Running locally
Requires Docker (for a Postgres + PostgREST stand-in that behaves like Supabase's REST
API) and Python 3.12 or newer.
```bash
pip install -r requirements.txt -r scripts/requirements-dev.txt
docker compose -f .local/docker-compose.yml up -d
python scripts/seed_supabase.py --env .local/.env.local
.local/run_local.sh # serves http://127.0.0.1:7861
python -m pytest -q tests # unit tests plus every public and admin flow
```
`.local/.env.local` holds throw-away local credentials only (and sets `COOKIE_SECURE=false`
because the local server is plain http). The real `.env` is ignored by git and must never
be uploaded to the Space (use Space secrets instead).
## Project layout
```
app/main.py routes (public, submission, admin)
app/services.py validation, filtering, approval logic
app/fields.py the single definition of every listing field and vocabulary
app/normalize.py turns messy multi-value text into vocabulary items
app/db.py Supabase REST (PostgREST) client
app/security.py sessions, CSRF tokens, rate limiting
app/templates/ Jinja2 templates
app/static/ style.css, app.js
scripts/ import_excel.py, seed_supabase.py
seed/ listings_seed.json generated from the workbook
supabase/schema.sql database schema
tests/ pytest suite
```