--- 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/`). 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://.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://-.hf.space/healthz`; it should report `"db_configured": true` and `"admin_configured": true`. 5. Share the direct address `https://-.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://-.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 ```