Skip to content

Commit e8ba1f1

Browse files
committed
backend changes
1 parent 946d254 commit e8ba1f1

8 files changed

Lines changed: 52 additions & 6 deletions

File tree

components/db/members.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { collection, getDocs } from "firebase/firestore"
12
import { Timestamp } from "firebase/firestore"
23
import { useMemo } from "react"
34
import { useAsync } from "react-async-hook"
45
import { currentGeneralCourt } from "functions/src/shared"
6+
import { firestore } from "../firebase"
57
import { loadDoc } from "./common"
68
export type CommitteeReference = {
79
CommitteeCode: string
@@ -55,6 +57,11 @@ export function useMemberSearch() {
5557
return { index, loading }
5658
}
5759

60+
export function useClaimedMemberCodes() {
61+
const { result: claimedCodes, loading } = useAsync(getClaimedMemberCodes, [])
62+
return { claimedCodes, loading }
63+
}
64+
5865
async function getMember(
5966
court: number,
6067
memberCode?: string
@@ -69,3 +76,8 @@ async function getMemberSearchIndex(): Promise<MemberSearchIndex | undefined> {
6976
`/generalCourts/${currentGeneralCourt}/indexes/memberSearch`
7077
) as any
7178
}
79+
80+
async function getClaimedMemberCodes(): Promise<Set<string>> {
81+
const snap = await getDocs(collection(firestore, "claimedMemberCodes"))
82+
return new Set(snap.docs.map(d => d.id))
83+
}

components/db/profile/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,6 @@ export type Profile = {
4646
phoneVerified?: boolean
4747
memberId?: string
4848
website?: string
49+
memberCode?: string
50+
legislatorBiography?: string
4951
}

firestore.rules

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ service cloud.firestore {
3838
// Only the completePhoneVerification cloud function (Admin SDK) sets phoneVerified
3939
return !request.resource.data.diff(resource.data).affectedKeys().hasAny(['phoneVerified'])
4040
}
41+
function doesNotChangeMemberCode() {
42+
// memberCode is set at signup and is immutable - only Admin SDK can write it
43+
return !request.resource.data.diff(resource.data).affectedKeys().hasAny(['memberCode'])
44+
}
4145
// either the change doesn't include the public field,
4246
// or the user is a base user (i.e. not an org)
4347
function validPublicChange() {
@@ -56,7 +60,7 @@ service cloud.firestore {
5660

5761
// Allow users to make updates except to delete their profile or set the role field.
5862
// Only admins can delete a user profile or set the user role field.
59-
allow update: if validUser() && doesNotChangeRole() && validPublicChange() && doesNotChangeNextDigestAt() && doesNotChangePhoneVerified()
63+
allow update: if validUser() && doesNotChangeRole() && doesNotChangeMemberCode() && validPublicChange() && doesNotChangeNextDigestAt() && doesNotChangePhoneVerified()
6064
}
6165
// Allow querying publications individually or with a collection group.
6266
match /{path=**}/publishedTestimony/{id} {
@@ -99,6 +103,10 @@ service cloud.firestore {
99103
allow read, write: if request.auth.token.get("role", "user") == "admin"
100104
}
101105
}
106+
match /claimedMemberCodes/{memberCode} {
107+
allow read: if true;
108+
allow write: if false;
109+
}
102110
match /ballotQuestions/{id} {
103111
allow read: if true;
104112
allow write: if false;

functions/src/auth/setRole.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,9 @@ const updateTestimony = async (
7575
const isPublic = (profile: Profile | undefined, role: Role) => {
7676
switch (role) {
7777
case "pendingUpgrade":
78+
case "pendingLegislator":
7879
case "admin":
79-
return false
8080
case "legislator":
81-
case "pendingUpgrade":
8281
return false
8382
case "organization":
8483
return true

functions/src/auth/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const Role = Union(
66
L("admin"),
77
L("legislator"),
88
L("pendingUpgrade"),
9+
L("pendingLegislator"),
910
L("organization")
1011
)
1112
export type Role = Static<typeof Role>
@@ -15,6 +16,7 @@ export const ZRole = z.enum([
1516
"admin",
1617
"legislator",
1718
"pendingUpgrade",
19+
"pendingLegislator",
1820
"organization"
1921
])
2022

functions/src/profile/finishSignup.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { checkRequestZod, checkAuth } from "../common"
55
import { setRole } from "../auth"
66

77
const CreateProfileRequest = z.object({
8-
requestedRole: z.enum(["user", "organization", "pendingUpgrade"])
8+
requestedRole: z.enum(["user", "organization", "pendingUpgrade", "pendingLegislator"])
99
})
1010

1111
export const finishSignup = functions.https.onCall(async (data, context) => {
@@ -18,6 +18,7 @@ export const finishSignup = functions.https.onCall(async (data, context) => {
1818
orgCategories,
1919
notificationFrequency,
2020
email,
21+
memberCode,
2122
public: isPublic
2223
} = data
2324

@@ -31,6 +32,15 @@ export const finishSignup = functions.https.onCall(async (data, context) => {
3132
uid,
3233
newProfile: { fullName, email, orgCategories }
3334
})
35+
} else if (requestedRole === "pendingLegislator") {
36+
await setRole({
37+
role: "pendingLegislator",
38+
auth,
39+
db,
40+
uid,
41+
newProfile: { fullName, email, memberCode }
42+
})
43+
await db.doc(`/claimedMemberCodes/${memberCode}`).set({ uid })
3444
} else {
3545
await setRole({
3646
role: "user",

functions/src/profile/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ export const Profile = Record({
3434
billsFollowing: Optional(Array(String)),
3535
contactInfo: Optional(Dictionary(String)),
3636
location: Optional(String),
37-
phoneVerified: Optional(Boolean)
37+
phoneVerified: Optional(Boolean),
38+
memberCode: Optional(String),
39+
legislatorBiography: Optional(String)
3840
})
3941

4042
export type Profile = Static<typeof Profile>

pages/api/users/[uid].ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ const ROLES = [
3333
"user", // Regular old
3434
"admin", // Can do anything, set in the db manually for now
3535
"pendingUpgrade", // Sign up as org, admin has to manually upgrade
36-
"organization" // An upgraded organization approved by an admin
36+
"organization", // An upgraded organization approved by an admin
37+
"pendingLegislator", // Sign up as legislator, admin has to manually approve
38+
"legislator" // An approved legislator account
3739
] as const
3840

3941
const BodySchema = z.object({
@@ -68,6 +70,15 @@ async function patch(req: NextApiRequest, res: NextApiResponse) {
6870
try {
6971
const user = await auth.getUser(uid)
7072

73+
// When rejecting a pending legislator request, release their claimed member code
74+
if (role === "user") {
75+
const profileSnap = await db.doc(`/profiles/${uid}`).get()
76+
const memberCode = profileSnap.data()?.memberCode
77+
if (memberCode) {
78+
await db.doc(`/claimedMemberCodes/${memberCode}`).delete()
79+
}
80+
}
81+
7182
await setRole({
7283
uid: user.uid,
7384
role,

0 commit comments

Comments
 (0)