Screenshots for oddbit/shrtnr issue: improve the API keys admin page UI.
api-keys-current.png— current implementationapi-keys-proposed.png— proposed design (Claude Design)
| export const updateUserRoles = functions.firestore | |
| .document('/users/{userId}') | |
| .onUpdate(async (change, context) => { | |
| const beforeData = change.before.data(); | |
| const afterData = change.after.data(); | |
| // Check if roles have changed | |
| if (JSON.stringify(beforeData.roles) === JSON.stringify(afterData.roles)) { | |
| functions.logger.info('Roles are unchanged. Do nothing.'); | |
| return null; |
| service cloud.firestore { | |
| match /databases/{database}/documents { | |
| // Alt A: Using roles stored in Firestore user documents to determine access | |
| match /collection-a/{document} { | |
| allow read: if 'admin' in getUserRoles(); | |
| } | |
| // Alt B: Using auth claims (role as an array) to determine access | |
| match /collection-b/{document} { |
| # Deploy the project only on push. | |
| - name: Deploy to Firebase | |
| if: github.event_name == 'push' | |
| # Only deploy hosting here... functions etc are deployed separately | |
| run: >- | |
| firebase | |
| deploy | |
| --only hosting | |
| -m "Github action run $GITHUB_RUN_ID" | |
| --non-interactive |
| name: Hosting | |
| on: | |
| pull_request: | |
| branches: | |
| - development | |
| - staging | |
| - main | |
| push: | |
| branches: | |
| - development |
| name: Snapshot backup | |
| env: | |
| BACKUP_BUCKET: "gs://example-prod-backups" | |
| FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} | |
| on: | |
| schedule: | |
| # At some schedule when your app is least active | |
| - cron: "0 3 * * *" | |
| jobs: |
| name: Backup archive | |
| env: | |
| BACKUP_BUCKET: "gs://example-prod-backups" | |
| FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} | |
| on: | |
| schedule: | |
| # First of every month at some time when your app is least active | |
| - cron: "0 3 1 * *" | |
| jobs: |
| name: Restore staging environment | |
| env: | |
| FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} | |
| on: | |
| pull_request: | |
| branches: | |
| - master | |
| jobs: | |
| remove-staging-backups: | |
| runs-on: ubuntu-latest |
| name: Publish package to pub.dev | |
| on: | |
| push: | |
| branches: | |
| - master | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| container: | |
| image: google/dart:latest |
| pipeline | |
| .apply("Receive PubSub", PubsubIO.readMessagesWithAttributes().fromTopic(fromTopic)) | |
| .apply("Transform to KeyValue", ParDo.of(new TransformPubSubMessage())) | |
| .apply("Apply window of time", Window.into(FixedWindows.of(WINDOW_SIZE))) | |
| .apply("Group by contestant", GroupByKey.create()) | |
| .apply("Count votes", Combine.groupedValues(new CountFn())) | |
| .apply("Transform to PubSub Message", ParDo.of(new MakePubSubPayload())) | |
| .apply("Send PubSub", PubsubIO.writeMessages().to(toTopic)); |