From 3ebf36e08a6f04de54d5b743fc4ef3974d0e25ed Mon Sep 17 00:00:00 2001 From: William Zhang <48934138+dhj03@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:22:36 +1000 Subject: [PATCH] User gender (backend) (#437) * feat: wrote DB migration for user gender * fix: removed 'Other' gender * feat: fully implemented user gender in backend * fix: set user gender to non-nullable * dev(frontend): added pronouns and gender field on signup * fix: re-arranged gender options * fix: user signup works with gender --------- Co-authored-by: m4ch374 --- .../2023-06-07-042751_user_gender/down.sql | 4 +++ .../2023-06-07-042751_user_gender/up.sql | 4 +++ backend/seed_data/src/seed.rs | 9 ++++- backend/server/src/auth.rs | 3 ++ backend/server/src/database/models.rs | 5 +-- backend/server/src/database/schema.rs | 12 +++++++ frontend/src/api/index.ts | 4 +++ .../pages/signup/SignupGenderSelection.tsx | 17 +++++++++ frontend/src/pages/signup/index.tsx | 35 ++++++++++++++++++- frontend/src/types/api.ts | 4 +++ 10 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 backend/migrations/2023-06-07-042751_user_gender/down.sql create mode 100644 backend/migrations/2023-06-07-042751_user_gender/up.sql create mode 100644 frontend/src/pages/signup/SignupGenderSelection.tsx diff --git a/backend/migrations/2023-06-07-042751_user_gender/down.sql b/backend/migrations/2023-06-07-042751_user_gender/down.sql new file mode 100644 index 00000000..0c5e3ef5 --- /dev/null +++ b/backend/migrations/2023-06-07-042751_user_gender/down.sql @@ -0,0 +1,4 @@ +ALTER TABLE users +DROP COLUMN gender; + +DROP TYPE user_gender; diff --git a/backend/migrations/2023-06-07-042751_user_gender/up.sql b/backend/migrations/2023-06-07-042751_user_gender/up.sql new file mode 100644 index 00000000..d6007cd3 --- /dev/null +++ b/backend/migrations/2023-06-07-042751_user_gender/up.sql @@ -0,0 +1,4 @@ +CREATE TYPE user_gender AS ENUM ('Female', 'Male', 'Unspecified'); + +ALTER TABLE users + ADD COLUMN gender user_gender DEFAULT 'Unspecified' NOT NULL; diff --git a/backend/seed_data/src/seed.rs b/backend/seed_data/src/seed.rs index 668c0ed4..c34e0ad9 100644 --- a/backend/seed_data/src/seed.rs +++ b/backend/seed_data/src/seed.rs @@ -1,7 +1,7 @@ #![allow(unused_variables)] use backend::database::models::*; -use backend::database::schema::{AdminLevel, ApplicationStatus}; +use backend::database::schema::{AdminLevel, ApplicationStatus, UserGender}; use backend::images::{save_image, try_decode_bytes}; use chrono::naive::NaiveDate; use diesel::pg::PgConnection; @@ -24,6 +24,7 @@ pub fn seed() { display_name: "Shrey Somaiya".to_string(), degree_name: "B. CompSci".to_string(), degree_starting_year: 2019, + gender: UserGender::Unspecified, superuser: true, }, NewUser { @@ -32,6 +33,7 @@ pub fn seed() { display_name: "Fake User".to_string(), degree_name: "B. CompSci".to_string(), degree_starting_year: 2019, + gender: UserGender::Unspecified, superuser: false, }, NewUser { @@ -40,6 +42,7 @@ pub fn seed() { display_name: "Michael Gribben".to_string(), degree_name: "B. Eng (Software)".to_string(), degree_starting_year: 2019, + gender: UserGender::Male, superuser: false, }, NewUser { @@ -48,6 +51,7 @@ pub fn seed() { display_name: "Giuliana Debellis".to_string(), degree_name: "B. CompSci".to_string(), degree_starting_year: 2020, + gender: UserGender::Female, superuser: false, }, NewUser { @@ -56,6 +60,7 @@ pub fn seed() { display_name: "Lachlan Ting".to_string(), degree_name: "B. CompSci".to_string(), degree_starting_year: 2019, + gender: UserGender::Male, superuser: false, }, NewUser { @@ -64,6 +69,7 @@ pub fn seed() { display_name: "Hayes Choi".to_string(), degree_name: "B. CompSci".to_string(), degree_starting_year: 2020, + gender: UserGender::Male, superuser: false, }, NewUser { @@ -72,6 +78,7 @@ pub fn seed() { display_name: "Clarence Feng".to_string(), degree_name: "B. CompSci".to_string(), degree_starting_year: 2020, + gender: UserGender::Male, superuser: false, }, ]; diff --git a/backend/server/src/auth.rs b/backend/server/src/auth.rs index d9a45aec..1820b939 100644 --- a/backend/server/src/auth.rs +++ b/backend/server/src/auth.rs @@ -2,6 +2,7 @@ use crate::error::JsonErr; use crate::{ database::{ models::{NewUser, User}, + schema::{UserGender}, Database, }, state::ApiState, @@ -279,6 +280,7 @@ pub struct SignUpBody { display_name: String, degree_name: String, degree_starting_year: u32, + gender: UserGender, } #[derive(Serialize)] @@ -347,6 +349,7 @@ pub async fn signup( display_name: body.display_name.to_string(), degree_name: body.degree_name.to_string(), degree_starting_year: body.degree_starting_year as i32, + gender: body.gender, superuser: User::get_number(conn) == 0, }; diff --git a/backend/server/src/database/models.rs b/backend/server/src/database/models.rs index 679f733c..ded34e94 100644 --- a/backend/server/src/database/models.rs +++ b/backend/server/src/database/models.rs @@ -1,11 +1,10 @@ use crate::images::{get_http_image_path, ImageLocation}; -use super::schema::AdminLevel; -use super::schema::ApplicationStatus; use super::schema::{ answers, applications, campaigns, comments, organisation_users, organisations, questions, ratings, roles, users, }; +use super::schema::{AdminLevel, ApplicationStatus, UserGender}; use chrono::NaiveDateTime; use chrono::Utc; use diesel::prelude::*; @@ -24,6 +23,7 @@ pub struct User { pub display_name: String, pub degree_name: String, pub degree_starting_year: i32, + pub gender: UserGender, pub superuser: bool, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, @@ -50,6 +50,7 @@ pub struct NewUser { pub display_name: String, pub degree_name: String, pub degree_starting_year: i32, + pub gender: UserGender, pub superuser: bool, } diff --git a/backend/server/src/database/schema.rs b/backend/server/src/database/schema.rs index 3cbe94fd..783138d4 100644 --- a/backend/server/src/database/schema.rs +++ b/backend/server/src/database/schema.rs @@ -25,6 +25,14 @@ impl AdminLevel { } } +#[derive(Debug, DbEnum, PartialEq, Serialize, Deserialize, Clone, Copy)] +#[DbValueStyle = "PascalCase"] +pub enum UserGender { + Female, + Male, + Unspecified, +} + table! { answers (id) { id -> Int4, @@ -142,6 +150,9 @@ table! { } table! { + use diesel::sql_types::*; + use super::UserGenderMapping; + users (id) { id -> Int4, email -> Text, @@ -149,6 +160,7 @@ table! { display_name -> Text, degree_name -> Text, degree_starting_year -> Int4, + gender -> UserGenderMapping, superuser -> Bool, created_at -> Timestamp, updated_at -> Timestamp, diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 44cac326..95b5cc40 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -22,6 +22,7 @@ import type { Role, RoleApplications, RoleInput, + UserGender, UserResponse, } from "../types/api"; @@ -39,11 +40,13 @@ export const doSignup = async ({ degree_name, zid, starting_year, + gender, }: { name: string; degree_name: string; zid: string; starting_year: number; + gender: UserGender; }) => API.request<{ token: string }>({ method: "POST", @@ -54,6 +57,7 @@ export const doSignup = async ({ display_name: name, degree_starting_year: starting_year, degree_name, + gender, }, }); diff --git a/frontend/src/pages/signup/SignupGenderSelection.tsx b/frontend/src/pages/signup/SignupGenderSelection.tsx new file mode 100644 index 00000000..2bf856d0 --- /dev/null +++ b/frontend/src/pages/signup/SignupGenderSelection.tsx @@ -0,0 +1,17 @@ +import tw from "twin.macro"; + +import type { ReactNode } from "react"; +import type React from "react"; + +const Select = tw.select` + form-select w-96 rounded-md + border-gray-300 shadow-sm + transition hocus:border-blue-300 focus:(ring ring-blue-200/50) +`; + +const Label = tw.label`flex flex-col`; +const LabelText: React.FC<{ children: ReactNode }> = ({ children }) => ( + {children} +); + +export default Object.assign(Select, { Label, LabelText }); diff --git a/frontend/src/pages/signup/index.tsx b/frontend/src/pages/signup/index.tsx index f6bd1642..c091cfa5 100644 --- a/frontend/src/pages/signup/index.tsx +++ b/frontend/src/pages/signup/index.tsx @@ -11,12 +11,28 @@ import Input from "components/Input"; import { doSignup } from "../../api"; import { getStore, setStore } from "../../utils"; +import Select from "./SignupGenderSelection"; + +import type { UserGender } from "types/api"; + +// I had to do it +/* eslint-disable @typescript-eslint/naming-convention */ +type TFormData = { + zid: string; + name: string; + degree_name: string; + starting_year: number; + gender: UserGender; +}; +/* eslint-enable @typescript-eslint/naming-convention */ + const Signup = () => { - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ zid: "", name: getStore("name") || "", degree_name: "", starting_year: new Date().getFullYear(), + gender: "Unspecified", }); const navigate = useNavigate(); @@ -99,6 +115,23 @@ const Signup = () => { max={new Date().getFullYear() + 10} /> + + + Gender + +