* Set up Firestore structure for mana bounty links
* Split up manalinks into successes and failures
* Allow clients to create manalinks
* Track txnId and successful users
* Store custom amounts in the link
* List all manalinks you've created
* Support backend for claiming manalinks
* Add some more error handling
* Tweak readme
* Fix typescript breakage
* Revert "Convert common imports in functions to be absolute"
This reverts commit c03518e906.
* Scaffolding so `claimManalink` works
* Clean up imports
* Barebones endpoint to claim mana
* Fix rules to only allow link creators to query
* Design out claim giftcard
* List all claimed transactions
* Style in a more awesome card
* Fix import
* Padding tweak
* Fix useManalinkTxns hook
* /send -> /link
* Tidy up some details
* Do a bunch of random manalinks work
* Fix up LinksTable to build
* Clean up LinksTable an absurd amount
* Basic details functionality on manalinks table
* Work on manalink claim stuff
* Fix up some merge mess
* Not-signed-in flow implemented
* Better manalinks table
* Only show outstanding links in table
* Use new `ManalinkTxn` type
* /link -> /links
* Change manalinks page UI to use nice looking tabs
* Many fixes to manalinks UI
* Default to 1 use
* Tidying up
* Some copy changes based on feedback
* Add required index
Co-authored-by: Marshall Polaris <marshall@pol.rs>
		
	
			
		
			
				
	
	
		
			146 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| rules_version = '2';
 | |
| 
 | |
| // To pick the right project: `firebase projects:list`, then `firebase use <project-name>`
 | |
| // To deploy: `firebase deploy --only firestore:rules`
 | |
| service cloud.firestore {
 | |
|   match /databases/{database}/documents {
 | |
| 
 | |
|     function isAdmin() {
 | |
|       return request.auth.uid == 'igi2zGXsfxYPgB0DJTXVJVmwCOr2' // Austin
 | |
|              || request.auth.uid == '5LZ4LgYuySdL1huCWe7bti02ghx2' // James
 | |
|              || request.auth.uid == 'tlmGNz9kjXc2EteizMORes4qvWl2' // Stephen
 | |
|              || request.auth.uid == 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2' // Manifold
 | |
|     }
 | |
| 
 | |
|     match /users/{userId} {
 | |
|       allow read;
 | |
|       allow update: if resource.data.id == request.auth.uid
 | |
|                        && request.resource.data.diff(resource.data).affectedKeys()
 | |
|                                                                     .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories']);
 | |
|     }
 | |
|     
 | |
|   	match /{somePath=**}/portfolioHistory/{portfolioHistoryId} {
 | |
|       allow read;
 | |
|     }
 | |
| 
 | |
|     match /users/{userId}/follows/{followUserId} {
 | |
|       allow read;
 | |
|       allow write: if request.auth.uid == userId;
 | |
|     }
 | |
| 
 | |
|     match /{somePath=**}/follows/{followUserId} {
 | |
|       allow read;
 | |
|     }
 | |
| 
 | |
|     match /private-users/{userId} {
 | |
|       allow read: if resource.data.id == request.auth.uid || isAdmin();
 | |
|       allow update: if (resource.data.id == request.auth.uid || isAdmin())
 | |
|                        && request.resource.data.diff(resource.data).affectedKeys()
 | |
|                        .hasOnly(['apiKey', 'unsubscribedFromResolutionEmails', 'unsubscribedFromCommentEmails', 'unsubscribedFromAnswerEmails', 'notificationPreferences' ]);
 | |
|     }
 | |
| 
 | |
|     match /private-users/{userId}/views/{viewId} {
 | |
|       allow create: if userId == request.auth.uid;
 | |
|     }
 | |
| 
 | |
|     match /private-users/{userId}/events/{eventId} {
 | |
|       allow create: if userId == request.auth.uid;
 | |
|     }
 | |
| 
 | |
|     match /private-users/{userId}/latency/{loadTimeId} {
 | |
|       allow create: if userId == request.auth.uid;
 | |
|     }
 | |
| 
 | |
|     match /private-users/{userId}/cache/{docId} {
 | |
|       allow read: if userId == request.auth.uid || isAdmin();
 | |
|     }
 | |
| 
 | |
|     match /contracts/{contractId} {
 | |
|       allow read;
 | |
|       allow update: if request.resource.data.diff(resource.data).affectedKeys()
 | |
|                                                                  .hasOnly(['tags', 'lowercaseTags']);
 | |
|       allow update: if request.resource.data.diff(resource.data).affectedKeys()
 | |
|                                                                  .hasOnly(['description', 'closeTime'])
 | |
|                        && resource.data.creatorId == request.auth.uid;
 | |
|       allow update: if isAdmin();
 | |
|           match /comments/{commentId} {
 | |
|             allow create: if request.auth != null && commentMatchesUser(request.auth.uid, request.resource.data);
 | |
|           }
 | |
|     }
 | |
| 
 | |
|     match /{somePath=**}/bets/{betId} {
 | |
|       allow read;
 | |
|     }
 | |
| 
 | |
|     match /{somePath=**}/liquidity/{liquidityId} {
 | |
|       allow read;
 | |
|     }
 | |
| 
 | |
|     function commentMatchesUser(userId, comment) {
 | |
|       // it's a bad look if someone can impersonate other ids/names/avatars so check everything
 | |
|       let user = get(/databases/$(database)/documents/users/$(userId));
 | |
|       return comment.userId == userId
 | |
|         && comment.userName == user.data.name
 | |
|         && comment.userUsername == user.data.username
 | |
|         && comment.userAvatarUrl == user.data.avatarUrl;
 | |
|     }
 | |
| 
 | |
|     match /{somePath=**}/comments/{commentId} {
 | |
|       allow read;
 | |
|     }
 | |
| 
 | |
|     match /{somePath=**}/answers/{answerId} {
 | |
|       allow read;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     match /{somePath=**}/followers/{userId} {
 | |
|       allow read;
 | |
|       allow create, update: if request.auth.uid == userId && request.resource.data.userId == userId;
 | |
|       allow delete: if request.auth.uid == userId;
 | |
|     }
 | |
| 
 | |
|     match /txns/{txnId} {
 | |
|       allow read;
 | |
|     }
 | |
| 
 | |
|     // Note: `resource` = existing doc, `request.resource` = incoming doc
 | |
|     match /manalinks/{slug} {
 | |
|       // Anyone can view any manalink
 | |
|       allow get;
 | |
|       // Only you can create a manalink with your fromId
 | |
|       allow create: if request.auth.uid == request.resource.data.fromId;
 | |
|       // Only you can list and change your own manalinks
 | |
|       allow list, update: if request.auth.uid == resource.data.fromId;
 | |
|     }
 | |
| 
 | |
|     match /users/{userId}/notifications/{notificationId} {
 | |
|       allow read;
 | |
|       allow update: if resource.data.userId == request.auth.uid
 | |
|                        && request.resource.data.diff(resource.data).affectedKeys()
 | |
|                                                                     .hasOnly(['isSeen', 'viewTime']);
 | |
|     }
 | |
| 
 | |
|      match /groups/{groupId} {
 | |
|           allow read;
 | |
|           allow update: if request.auth.uid == resource.data.creatorId
 | |
|                            && request.resource.data.diff(resource.data)
 | |
|                            .affectedKeys()
 | |
|                            .hasOnly(['name', 'about', 'contractIds', 'memberIds', 'anyoneCanJoin' ]);
 | |
|          allow update: if (request.auth.uid in resource.data.memberIds  || resource.data.anyoneCanJoin)
 | |
|                            && request.resource.data.diff(resource.data)
 | |
|                            .affectedKeys()
 | |
|                            .hasOnly([ 'contractIds', 'memberIds' ]);
 | |
|           allow delete: if request.auth.uid == resource.data.creatorId;
 | |
| 
 | |
|           function isMember() {
 | |
|             return request.auth.uid in get(/databases/$(database)/documents/groups/$(groupId)).data.memberIds;
 | |
|           }
 | |
|           match /comments/{commentId} {
 | |
|           	allow read;
 | |
|             allow create: if request.auth != null && commentMatchesUser(request.auth.uid, request.resource.data) && isMember();
 | |
|           }
 | |
|         }
 | |
|   }
 | |
| }
 |