What you’ll have
- Google sign-in, with new accounts held for approval
- A dashboard listing your boards, and multiple boards per user
- Roles doing real work: editors and admins can change things, everyone else reads
- The admin panel at
/admin/for approving and promoting users - PDF attachments on cards, stored in Google Cloud Storage
- A board exported to PDF on demand
Which parts of Feather this uses
Authentication
Google OAuth, the approval workflow,
@auth_required and @login_only.Admin panel
Scaffolded into your app, not hidden in the framework.
File storage
get_storage() covers local files and GCS behind one interface.PDF generation
WeasyPrint turns HTML and CSS into the exported document.
Step 1: Credentials
Google OAuth setup
Google OAuth setup
- Open APIs and Credentials.
- Create an OAuth 2.0 Client ID of type Web application.
- Add
http://localhost:5173/auth/google/callbackas an authorized redirect URI. - Copy the client ID and secret.
GCS setup
GCS setup
- Create a bucket in Cloud Storage.
- Create a service account in IAM.
- Grant it the Storage Object Admin role.
- Create a JSON key and copy the entire contents.
Step 2: Scaffold
This part needs a different app type, so start fresh.feather db migrate -m "Initial migration" and feather db upgrade first.
.env
Step 3: Run the first prompt
Two prompts, because asking for auth, storage and export in one go is asking for too much. The first gets you a working app behind sign-in.Prompt 1 of 2: auth and boards
Step 4: Run the second prompt
Once sign-in works and boards render, add the files and the export.Prompt 2 of 2: attachments and export
Step 5: Test it
- Sign in with Google. A brand new account should land on the pending page.
- Approve yourself from
/admin/usersas the admin created byseeds.py. - Create a board from the dashboard, then open it and add columns and cards.
- Go back and create a second board. Both should be listed.
- Attach a PDF to a card, then open it in the viewer.
- Click Export PDF and check the download.
- Open
/admin/and confirm the user list and analytics render.
user role from
/admin/users and confirm it sees the boards with no create, edit or delete controls.
What the framework did
You wrote no sign-in code. Google OAuth, the session, the callback and the user record are all Feather’s. What the prompt asked for was the domain model on top. See Authentication. The approval gate is two fields. A user is pending whenactive is false and
approved_at is null, and @auth_required raises AccountPendingError for them, which
redirects to the pending page instead of returning a bare 403. That’s why the pending page
itself uses @login_only. See Exceptions.
The admin panel is your code. It was scaffolded into routes/pages/admin.py and
templates/pages/admin/, so you can change any of it. See
Admin panel.
Uploads went through a storage interface. get_storage() returns the configured backend,
which is why the same code works against the local filesystem. Script-capable extensions
are refused by default, which is a real protection given files are served from your own
origin. See File storage.
Authorization ran twice, on purpose. The service refuses the action and the template
hides the button. Either alone is a bug: service-only shows controls that fail,
template-only is trivially bypassed.
Next: turn it into a SaaS
Tenant isolation, platform admin, and queries that can’t leak across companies.