From 7c33a4943ce7266fce2fdc290e0bcfad07df8754 Mon Sep 17 00:00:00 2001 From: Adrian Wozniak Date: Tue, 21 Apr 2020 08:35:08 +0200 Subject: [PATCH] Additional invitation stuff --- jirs-client/js/css/styledSelectChild.css | 5 ++ jirs-client/src/shared/styled_select_child.rs | 1 + jirs-client/src/users.rs | 7 ++- jirs-data/src/lib.rs | 44 +++++++++++++ jirs-data/src/sql.rs | 42 ++++++++++++- .../down.sql | 2 + .../up.sql | 16 +++++ jirs-server/src/models.rs | 29 ++++++++- jirs-server/src/schema.rs | 62 +++++++++++++++++++ 9 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 jirs-server/migrations/2020-04-20-172406_create_invitations/down.sql create mode 100644 jirs-server/migrations/2020-04-20-172406_create_invitations/up.sql diff --git a/jirs-client/js/css/styledSelectChild.css b/jirs-client/js/css/styledSelectChild.css index 5d1f59f5..98bb62e6 100644 --- a/jirs-client/js/css/styledSelectChild.css +++ b/jirs-client/js/css/styledSelectChild.css @@ -2,6 +2,11 @@ padding: 4px 8px; } +.selectItem.capitalize, +.optionItem.capitalize { + text-transform: capitalize; +} + .selectItem.priority.highest > .styledIcon, .optionItem.priority.highest > .styledIcon { color: var(--highest); diff --git a/jirs-client/src/shared/styled_select_child.rs b/jirs-client/src/shared/styled_select_child.rs index 1642fbe3..c14fec19 100644 --- a/jirs-client/src/shared/styled_select_child.rs +++ b/jirs-client/src/shared/styled_select_child.rs @@ -256,6 +256,7 @@ impl ToStyledSelectChild for jirs_data::UserRole { StyledSelectChild::build() .add_class(name.as_str()) + .add_class("capitalize") .text(name) .value(self.clone().into()) } diff --git a/jirs-client/src/users.rs b/jirs-client/src/users.rs index 84f05d97..6fc001eb 100644 --- a/jirs-client/src/users.rs +++ b/jirs-client/src/users.rs @@ -1,8 +1,9 @@ use seed::{prelude::*, *}; -use jirs_data::UserRole; use jirs_data::{ToVec, UsersFieldId}; +use jirs_data::{UserRole, WsMsg}; +use crate::api::send_ws_msg; use crate::model::{Model, Page, PageContent, UsersPage}; use crate::shared::styled_button::StyledButton; use crate::shared::styled_field::StyledField; @@ -50,6 +51,10 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { } pub fn view(model: &Model) -> Node { + if model.user.is_none() { + return empty![]; + } + let page = match &model.page_content { PageContent::Users(page) => page, _ => return empty![], diff --git a/jirs-data/src/lib.rs b/jirs-data/src/lib.rs index 1b78e020..6be191af 100644 --- a/jirs-data/src/lib.rs +++ b/jirs-data/src/lib.rs @@ -23,6 +23,7 @@ pub type ProjectId = i32; pub type UserId = i32; pub type CommentId = i32; pub type TokenId = i32; +pub type InvitationId = i32; pub type EmailString = String; pub type UsernameString = String; @@ -403,6 +404,31 @@ impl Into for u32 { } } +#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))] +#[cfg_attr(feature = "backend", sql_type = "InvitationStateType")] +#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)] +pub enum InvitationState { + Sent, + Accepted, + Revoked, +} + +impl Default for InvitationState { + fn default() -> Self { + InvitationState::Sent + } +} + +impl std::fmt::Display for InvitationState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InvitationState::Sent => f.write_str("sent"), + InvitationState::Accepted => f.write_str("accepted"), + InvitationState::Revoked => f.write_str("revoked"), + } + } +} + #[derive(Clone, Serialize, Debug, PartialEq)] pub struct ErrorResponse { pub errors: Vec, @@ -632,6 +658,24 @@ pub enum WsMsg { SignUpSuccess, SignUpPairTaken, + // invitations + InvitationListRequest, + InvitationListLoaded(Vec), + InvitedUsersRequest, + InvitedUsersLoaded(Vec), + InvitationSendRequest { + name: UsernameString, + email: EmailString, + }, + InvitationSendSuccess, + InvitationSendFailure, + // + InvitationRevokeRequest(InvitationId), + InvitationRevokeSuccess(InvitationId), + // + InvitationAcceptRequest(InvitationId), + InvitationAcceptSuccess(InvitationId), + // project page ProjectRequest, ProjectLoaded(Project), diff --git a/jirs-data/src/sql.rs b/jirs-data/src/sql.rs index de56860a..a641ac72 100644 --- a/jirs-data/src/sql.rs +++ b/jirs-data/src/sql.rs @@ -2,7 +2,7 @@ use std::io::Write; use diesel::{deserialize::*, pg::*, serialize::*, *}; -use crate::{IssuePriority, IssueStatus, IssueType, ProjectCategory, UserRole}; +use crate::{InvitationState, IssuePriority, IssueStatus, IssueType, ProjectCategory, UserRole}; #[derive(SqlType)] #[postgres(type_name = "IssuePriorityType")] @@ -201,3 +201,43 @@ impl ToSql for UserRole { Ok(IsNull::No) } } + +#[derive(SqlType)] +#[postgres(type_name = "InvitationStateType")] +pub struct InvitationStateType; + +impl diesel::query_builder::QueryId for InvitationState { + type QueryId = InvitationState; +} + +fn invitation_state_from_sql(bytes: Option<&[u8]>) -> deserialize::Result { + match not_none!(bytes) { + b"sent" => Ok(InvitationState::Sent), + b"accepted" => Ok(InvitationState::Accepted), + b"revoked" => Ok(InvitationState::Revoked), + _ => Ok(InvitationState::Sent), + } +} + +impl FromSql for InvitationState { + fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result { + invitation_state_from_sql(bytes) + } +} + +impl FromSql for InvitationState { + fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result { + invitation_state_from_sql(bytes) + } +} + +impl ToSql for InvitationState { + fn to_sql(&self, out: &mut Output) -> serialize::Result { + match *self { + InvitationState::Sent => out.write_all(b"sent")?, + InvitationState::Accepted => out.write_all(b"accepted")?, + InvitationState::Revoked => out.write_all(b"revoked")?, + } + Ok(IsNull::No) + } +} diff --git a/jirs-server/migrations/2020-04-20-172406_create_invitations/down.sql b/jirs-server/migrations/2020-04-20-172406_create_invitations/down.sql new file mode 100644 index 00000000..2633420b --- /dev/null +++ b/jirs-server/migrations/2020-04-20-172406_create_invitations/down.sql @@ -0,0 +1,2 @@ +drop TABLE IF EXISTS invitations CASCADE; +drop TYPE IF EXISTS "InvitationStateType" CASCADE; diff --git a/jirs-server/migrations/2020-04-20-172406_create_invitations/up.sql b/jirs-server/migrations/2020-04-20-172406_create_invitations/up.sql new file mode 100644 index 00000000..d973f4fd --- /dev/null +++ b/jirs-server/migrations/2020-04-20-172406_create_invitations/up.sql @@ -0,0 +1,16 @@ +create type "InvitationStateType" AS ENUM ( + 'sent', + 'accepted', + 'revoked' +); + +create table invitations ( + id serial primary key not null, + name text not null, + email text not null, + state "InvitationStateType" not null default 'sent', + project_id integer not null references projects (id), + invited_by_id integer not null references users (id), + created_at timestamp not null default now(), + updated_at timestamp not null default now() +); diff --git a/jirs-server/src/models.rs b/jirs-server/src/models.rs index 18498f90..c81e558e 100644 --- a/jirs-server/src/models.rs +++ b/jirs-server/src/models.rs @@ -2,7 +2,10 @@ use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use jirs_data::{IssuePriority, IssueStatus, IssueType, ProjectCategory, UserRole}; +use jirs_data::{ + InvitationState, InvitationStateType, IssuePriority, IssueStatus, IssueType, ProjectCategory, + UserRole, +}; use crate::schema::*; @@ -217,7 +220,6 @@ pub struct UserForm { } #[derive(Debug, Serialize, Deserialize, Queryable)] -#[serde(rename_all = "camelCase")] pub struct Token { pub id: i32, pub user_id: i32, @@ -242,7 +244,6 @@ impl Into for Token { } #[derive(Debug, Serialize, Deserialize, Insertable)] -#[serde(rename_all = "camelCase")] #[table_name = "tokens"] pub struct TokenForm { pub user_id: i32, @@ -250,3 +251,25 @@ pub struct TokenForm { pub refresh_token: Uuid, pub bind_token: Option, } + +#[derive(Debug, Serialize, Deserialize, Queryable)] +pub struct Invitation { + pub id: i32, + pub name: String, + pub email: String, + pub state: InvitationState, + pub project_id: i32, + pub invited_by_id: i32, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +#[derive(Debug, Serialize, Deserialize, Insertable)] +#[table_name = "invitations"] +pub struct InvitationForm { + pub name: String, + pub email: String, + pub state: InvitationState, + pub project_id: i32, + pub invited_by_id: i32, +} diff --git a/jirs-server/src/schema.rs b/jirs-server/src/schema.rs index fc142dbf..d9ece42d 100644 --- a/jirs-server/src/schema.rs +++ b/jirs-server/src/schema.rs @@ -47,6 +47,65 @@ table! { } } +table! { + use diesel::sql_types::*; + use jirs_data::sql::*; + + /// Representation of the `invitations` table. + /// + /// (Automatically generated by Diesel.) + invitations (id) { + /// The `id` column of the `invitations` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + id -> Int4, + /// The `name` column of the `invitations` table. + /// + /// Its SQL type is `Text`. + /// + /// (Automatically generated by Diesel.) + name -> Text, + /// The `email` column of the `invitations` table. + /// + /// Its SQL type is `Text`. + /// + /// (Automatically generated by Diesel.) + email -> Text, + /// The `state` column of the `invitations` table. + /// + /// Its SQL type is `InvitationStateType`. + /// + /// (Automatically generated by Diesel.) + state -> InvitationStateType, + /// The `project_id` column of the `invitations` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + project_id -> Int4, + /// The `invited_by_id` column of the `invitations` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + invited_by_id -> Int4, + /// The `created_at` column of the `invitations` table. + /// + /// Its SQL type is `Timestamp`. + /// + /// (Automatically generated by Diesel.) + created_at -> Timestamp, + /// The `updated_at` column of the `invitations` table. + /// + /// Its SQL type is `Timestamp`. + /// + /// (Automatically generated by Diesel.) + updated_at -> Timestamp, + } +} + table! { use diesel::sql_types::*; use jirs_data::sql::*; @@ -356,6 +415,8 @@ table! { joinable!(comments -> issues (issue_id)); joinable!(comments -> users (user_id)); +joinable!(invitations -> projects (project_id)); +joinable!(invitations -> users (invited_by_id)); joinable!(issue_assignees -> issues (issue_id)); joinable!(issue_assignees -> users (user_id)); joinable!(issues -> projects (project_id)); @@ -365,6 +426,7 @@ joinable!(users -> projects (project_id)); allow_tables_to_appear_in_same_query!( comments, + invitations, issue_assignees, issues, projects,