Additional invitation stuff

This commit is contained in:
Adrian Wozniak 2020-04-21 08:35:08 +02:00
parent 761305fbbd
commit 7c33a4943c
9 changed files with 203 additions and 5 deletions

View File

@ -2,6 +2,11 @@
padding: 4px 8px; padding: 4px 8px;
} }
.selectItem.capitalize,
.optionItem.capitalize {
text-transform: capitalize;
}
.selectItem.priority.highest > .styledIcon, .selectItem.priority.highest > .styledIcon,
.optionItem.priority.highest > .styledIcon { .optionItem.priority.highest > .styledIcon {
color: var(--highest); color: var(--highest);

View File

@ -256,6 +256,7 @@ impl ToStyledSelectChild for jirs_data::UserRole {
StyledSelectChild::build() StyledSelectChild::build()
.add_class(name.as_str()) .add_class(name.as_str())
.add_class("capitalize")
.text(name) .text(name)
.value(self.clone().into()) .value(self.clone().into())
} }

View File

@ -1,8 +1,9 @@
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use jirs_data::UserRole;
use jirs_data::{ToVec, UsersFieldId}; 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::model::{Model, Page, PageContent, UsersPage};
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_field::StyledField; use crate::shared::styled_field::StyledField;
@ -50,6 +51,10 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
} }
pub fn view(model: &Model) -> Node<Msg> { pub fn view(model: &Model) -> Node<Msg> {
if model.user.is_none() {
return empty![];
}
let page = match &model.page_content { let page = match &model.page_content {
PageContent::Users(page) => page, PageContent::Users(page) => page,
_ => return empty![], _ => return empty![],

View File

@ -23,6 +23,7 @@ pub type ProjectId = i32;
pub type UserId = i32; pub type UserId = i32;
pub type CommentId = i32; pub type CommentId = i32;
pub type TokenId = i32; pub type TokenId = i32;
pub type InvitationId = i32;
pub type EmailString = String; pub type EmailString = String;
pub type UsernameString = String; pub type UsernameString = String;
@ -403,6 +404,31 @@ impl Into<ProjectCategory> 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)] #[derive(Clone, Serialize, Debug, PartialEq)]
pub struct ErrorResponse { pub struct ErrorResponse {
pub errors: Vec<String>, pub errors: Vec<String>,
@ -632,6 +658,24 @@ pub enum WsMsg {
SignUpSuccess, SignUpSuccess,
SignUpPairTaken, SignUpPairTaken,
// invitations
InvitationListRequest,
InvitationListLoaded(Vec<Invitation>),
InvitedUsersRequest,
InvitedUsersLoaded(Vec<User>),
InvitationSendRequest {
name: UsernameString,
email: EmailString,
},
InvitationSendSuccess,
InvitationSendFailure,
//
InvitationRevokeRequest(InvitationId),
InvitationRevokeSuccess(InvitationId),
//
InvitationAcceptRequest(InvitationId),
InvitationAcceptSuccess(InvitationId),
// project page // project page
ProjectRequest, ProjectRequest,
ProjectLoaded(Project), ProjectLoaded(Project),

View File

@ -2,7 +2,7 @@ use std::io::Write;
use diesel::{deserialize::*, pg::*, serialize::*, *}; use diesel::{deserialize::*, pg::*, serialize::*, *};
use crate::{IssuePriority, IssueStatus, IssueType, ProjectCategory, UserRole}; use crate::{InvitationState, IssuePriority, IssueStatus, IssueType, ProjectCategory, UserRole};
#[derive(SqlType)] #[derive(SqlType)]
#[postgres(type_name = "IssuePriorityType")] #[postgres(type_name = "IssuePriorityType")]
@ -201,3 +201,43 @@ impl ToSql<UserRoleType, Pg> for UserRole {
Ok(IsNull::No) 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<InvitationState> {
match not_none!(bytes) {
b"sent" => Ok(InvitationState::Sent),
b"accepted" => Ok(InvitationState::Accepted),
b"revoked" => Ok(InvitationState::Revoked),
_ => Ok(InvitationState::Sent),
}
}
impl FromSql<InvitationStateType, Pg> for InvitationState {
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
invitation_state_from_sql(bytes)
}
}
impl FromSql<sql_types::Text, Pg> for InvitationState {
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
invitation_state_from_sql(bytes)
}
}
impl ToSql<InvitationStateType, Pg> for InvitationState {
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> 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)
}
}

View File

@ -0,0 +1,2 @@
drop TABLE IF EXISTS invitations CASCADE;
drop TYPE IF EXISTS "InvitationStateType" CASCADE;

View File

@ -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()
);

View File

@ -2,7 +2,10 @@ use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use jirs_data::{IssuePriority, IssueStatus, IssueType, ProjectCategory, UserRole}; use jirs_data::{
InvitationState, InvitationStateType, IssuePriority, IssueStatus, IssueType, ProjectCategory,
UserRole,
};
use crate::schema::*; use crate::schema::*;
@ -217,7 +220,6 @@ pub struct UserForm {
} }
#[derive(Debug, Serialize, Deserialize, Queryable)] #[derive(Debug, Serialize, Deserialize, Queryable)]
#[serde(rename_all = "camelCase")]
pub struct Token { pub struct Token {
pub id: i32, pub id: i32,
pub user_id: i32, pub user_id: i32,
@ -242,7 +244,6 @@ impl Into<jirs_data::Token> for Token {
} }
#[derive(Debug, Serialize, Deserialize, Insertable)] #[derive(Debug, Serialize, Deserialize, Insertable)]
#[serde(rename_all = "camelCase")]
#[table_name = "tokens"] #[table_name = "tokens"]
pub struct TokenForm { pub struct TokenForm {
pub user_id: i32, pub user_id: i32,
@ -250,3 +251,25 @@ pub struct TokenForm {
pub refresh_token: Uuid, pub refresh_token: Uuid,
pub bind_token: Option<Uuid>, pub bind_token: Option<Uuid>,
} }
#[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,
}

View File

@ -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! { table! {
use diesel::sql_types::*; use diesel::sql_types::*;
use jirs_data::sql::*; use jirs_data::sql::*;
@ -356,6 +415,8 @@ table! {
joinable!(comments -> issues (issue_id)); joinable!(comments -> issues (issue_id));
joinable!(comments -> users (user_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 -> issues (issue_id));
joinable!(issue_assignees -> users (user_id)); joinable!(issue_assignees -> users (user_id));
joinable!(issues -> projects (project_id)); joinable!(issues -> projects (project_id));
@ -365,6 +426,7 @@ joinable!(users -> projects (project_id));
allow_tables_to_appear_in_same_query!( allow_tables_to_appear_in_same_query!(
comments, comments,
invitations,
issue_assignees, issue_assignees,
issues, issues,
projects, projects,