Use custom types over text in database

This commit is contained in:
Adrian Woźniak 2020-04-01 13:29:43 +02:00
parent 5758c79f35
commit 4a9ba8e2a3
18 changed files with 469 additions and 74 deletions

1
Cargo.lock generated
View File

@ -1055,6 +1055,7 @@ name = "jirs-data"
version = "0.1.0"
dependencies = [
"chrono",
"diesel",
"serde",
"serde_json",
"uuid 0.8.1",

View File

@ -30,14 +30,12 @@
}
.modal > .clickableOverlay > .styledModal.center {
/*max-width: ${props => props.width}px;*/
vertical-align: middle;
border-radius: 3px;
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);
}
.modal > .clickableOverlay > .styledModal.aside {
/*max-width: ${props => props.width}px;*/
min-height: 100vh;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
}

View File

@ -106,6 +106,7 @@ impl Default for Model {
}
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug)]
pub enum Icon {
Bug,

View File

@ -1,6 +1,6 @@
use seed::{prelude::*, *};
use jirs_data::{FullProject, Issue, IssuePriority, IssueType, UpdateIssuePayload};
use jirs_data::*;
use crate::model::{Icon, Model, Page};
use crate::shared::styled_avatar::StyledAvatar;
@ -251,8 +251,6 @@ fn avatars_filters(model: &Model) -> Node<Msg> {
}
fn project_board_lists(model: &Model) -> Node<Msg> {
use jirs_data::IssueStatus;
div![
id!["projectBoardLists"],
project_issue_list(model, IssueStatus::Backlog),
@ -317,28 +315,25 @@ fn project_issue(model: &Model, project: &FullProject, issue: &Issue) -> Node<Ms
})
.collect();
let issue_type_icon = match issue.issue_type.parse::<IssueType>() {
Ok(icon) => {
let mut node = crate::shared::styled_icon(icon.into());
node.add_style(
St::Color,
format!("var(--{issue_type})", issue_type = issue.issue_type),
);
node
}
Err(e) => span![format!("{}", e)],
let issue_type_icon = {
let mut node = crate::shared::styled_icon(issue.issue_type.clone().into());
node.add_style(
St::Color,
format!(
"var(--{issue_type})",
issue_type = issue.issue_type.to_string()
),
);
node
};
let priority_icon = match issue.priority.parse::<IssuePriority>() {
Ok(p) => {
let icon = match p {
IssuePriority::Low | IssuePriority::Lowest => Icon::ArrowDown,
_ => Icon::ArrowUp,
};
let mut node = crate::shared::styled_icon(icon);
node.add_style(St::Color, format!("var(--{})", p.to_lower_name()));
node
}
Err(e) => span![e.clone()],
let priority_icon = {
let icon = match issue.priority {
IssuePriority::Low | IssuePriority::Lowest => Icon::ArrowDown,
_ => Icon::ArrowUp,
};
let mut node = crate::shared::styled_icon(icon);
node.add_style(St::Color, format!("var(--{})", issue.priority));
node
};
let issue_id = issue.id;

View File

@ -1,4 +1,4 @@
use seed::{prelude::*, *};
use seed::prelude::*;
use crate::shared::inner_layout;
use crate::{model, Msg};

View File

@ -4,6 +4,7 @@ use crate::model::Icon;
use crate::shared::{styled_icon, ToNode};
use crate::Msg;
#[allow(dead_code)]
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub enum Variant {
Center,
@ -54,12 +55,16 @@ pub fn render(values: Modal) -> Node<Msg> {
} else {
empty![]
};
let clickable_class = format!("clickableOverlay {}", variant.to_class_name());
let styled_modal_class = format!("styledModal {}", variant.to_class_name());
let styled_modal_style = format!("max-width: {width}px", width = width);
div![
attrs![At::Class => "modal"],
div![
attrs![At::Class => format!("clickableOverlay {}", variant.to_class_name())],
attrs![At::Class => clickable_class],
div![
attrs![At::Class => format!("styledModal {}", variant.to_class_name())],
attrs![At::Class => styled_modal_class, At::Style => styled_modal_style],
icon,
children
]

View File

@ -4,6 +4,7 @@ use crate::model::Icon;
use crate::shared::{styled_icon, ToNode};
use crate::Msg;
#[allow(dead_code)]
pub enum Variant {
Primary,
Success,

View File

@ -12,10 +12,16 @@ license = "MPL-2.0"
name = "jirs_data"
path = "./src/lib.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
backend = [ "diesel" ]
[dependencies]
serde = "*"
serde_json = "*"
chrono = { version = "*", features = [ "serde" ] }
uuid = { version = ">=0.7.0, <0.9.0", features = ["serde"] }
[dependencies.diesel]
optional = true
version = "1.4.4"
features = [ "unstable", "postgres", "numeric", "extras", "uuidv07" ]

View File

@ -1,15 +1,25 @@
use std::str::FromStr;
use chrono::NaiveDateTime;
#[cfg(feature = "backend")]
use diesel::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[cfg(feature = "backend")]
pub use sql::*;
#[cfg(feature = "backend")]
pub mod sql;
pub trait ResponseData {
type Response: Serialize;
fn into_response(self) -> Self::Response;
}
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
#[cfg_attr(feature = "backend", sql_type = "IssueTypeType")]
#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum IssueType {
@ -31,14 +41,13 @@ impl FromStr for IssueType {
}
}
impl ToString for IssueType {
fn to_string(&self) -> String {
impl std::fmt::Display for IssueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IssueType::Task => "Task",
IssueType::Bug => "Bug",
IssueType::Story => "Story",
IssueType::Task => f.write_str("task"),
IssueType::Bug => f.write_str("bug"),
IssueType::Story => f.write_str("story"),
}
.to_string()
}
}
@ -88,6 +97,8 @@ impl IssueStatus {
}
}
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
#[cfg_attr(feature = "backend", sql_type = "IssuePriorityType")]
#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq)]
pub enum IssuePriority {
Highest,
@ -112,6 +123,18 @@ impl FromStr for IssuePriority {
}
}
impl std::fmt::Display for IssuePriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IssuePriority::Highest => f.write_str("highest"),
IssuePriority::High => f.write_str("high"),
IssuePriority::Medium => f.write_str("medium"),
IssuePriority::Low => f.write_str("low"),
IssuePriority::Lowest => f.write_str("lowest"),
}
}
}
impl IssuePriority {
pub fn to_text_value(&self) -> &str {
match self {
@ -123,16 +146,6 @@ impl IssuePriority {
}
}
pub fn to_lower_name(&self) -> &str {
match self {
IssuePriority::Highest => "highest",
IssuePriority::High => "high",
IssuePriority::Medium => "medium",
IssuePriority::Low => "low",
IssuePriority::Lowest => "lowest",
}
}
pub fn to_value(&self) -> i32 {
match self {
IssuePriority::Highest => 5,
@ -185,9 +198,9 @@ pub struct FullIssue {
pub id: i32,
pub title: String,
#[serde(rename = "type")]
pub issue_type: String,
pub issue_type: IssueType,
pub status: String,
pub priority: String,
pub priority: IssuePriority,
pub list_position: f64,
pub description: Option<String>,
pub description_text: Option<String>,
@ -235,9 +248,9 @@ pub struct Issue {
pub id: i32,
pub title: String,
#[serde(rename = "type")]
pub issue_type: String,
pub issue_type: IssueType,
pub status: IssueStatus,
pub priority: String,
pub priority: IssuePriority,
pub list_position: f64,
pub description: Option<String>,
pub description_text: Option<String>,
@ -293,9 +306,9 @@ pub struct Token {
pub struct UpdateIssuePayload {
pub title: Option<String>,
#[serde(rename = "type")]
pub issue_type: Option<String>,
pub issue_type: Option<IssueType>,
pub status: Option<String>,
pub priority: Option<String>,
pub priority: Option<IssuePriority>,
pub list_position: Option<f64>,
pub description: Option<Option<String>>,
pub description_text: Option<Option<String>>,
@ -327,9 +340,9 @@ pub struct UpdateCommentPayload {
pub struct CreateIssuePayload {
pub title: String,
#[serde(rename = "type")]
pub issue_type: String,
pub issue_type: IssueType,
pub status: String,
pub priority: String,
pub priority: IssuePriority,
pub description: Option<String>,
pub description_text: Option<String>,
pub estimate: Option<i32>,

81
jirs-data/src/sql.rs Normal file
View File

@ -0,0 +1,81 @@
use std::io::Write;
use diesel::{deserialize::*, pg::*, serialize::*, *};
use crate::{IssuePriority, IssueType};
#[derive(SqlType)]
#[postgres(type_name = "IssuePriorityType")]
pub struct IssuePriorityType;
impl ToSql<IssuePriorityType, Pg> for IssuePriority {
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
match *self {
IssuePriority::Highest => out.write_all(b"highest")?,
IssuePriority::High => out.write_all(b"high")?,
IssuePriority::Medium => out.write_all(b"medium")?,
IssuePriority::Low => out.write_all(b"low")?,
IssuePriority::Lowest => out.write_all(b"lowest")?,
}
Ok(IsNull::No)
}
}
fn issue_priority_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<IssuePriority> {
match not_none!(bytes) {
b"5" | b"highest" => Ok(IssuePriority::Highest),
b"4" | b"high" => Ok(IssuePriority::High),
b"3" | b"medium" => Ok(IssuePriority::Medium),
b"2" | b"low" => Ok(IssuePriority::Low),
b"1" | b"lowest" => Ok(IssuePriority::Lowest),
_ => Ok(IssuePriority::Lowest),
}
}
impl FromSql<IssuePriorityType, Pg> for IssuePriority {
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
issue_priority_from_sql(bytes)
}
}
impl FromSql<sql_types::Text, Pg> for IssuePriority {
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
issue_priority_from_sql(bytes)
}
}
#[derive(SqlType)]
#[postgres(type_name = "IssueTypeType")]
pub struct IssueTypeType;
fn issue_type_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<IssueType> {
match not_none!(bytes) {
b"task" => Ok(IssueType::Task),
b"bug" => Ok(IssueType::Bug),
b"story" => Ok(IssueType::Story),
_ => Ok(IssueType::Task),
}
}
impl FromSql<IssueTypeType, Pg> for IssueType {
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
issue_type_from_sql(bytes)
}
}
impl FromSql<sql_types::Text, Pg> for IssueType {
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
issue_type_from_sql(bytes)
}
}
impl ToSql<IssueTypeType, Pg> for IssueType {
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
match *self {
IssueType::Task => out.write_all(b"task")?,
IssueType::Story => out.write_all(b"story")?,
IssueType::Bug => out.write_all(b"bug")?,
}
Ok(IsNull::No)
}
}

View File

@ -13,7 +13,6 @@ name = "jirs_server"
path = "./src/main.rs"
[dependencies]
jirs-data = { path = "../jirs-data" }
serde = { version = "*", features = ["derive"] }
actix = { version = "*" }
actix-web = { version = "*" }
@ -45,3 +44,6 @@ futures = { version = "*" }
version = "1.4.4"
features = [ "unstable", "postgres", "numeric", "extras", "uuidv07" ]
[dependencies.jirs-data]
path = "../jirs-data"
features = [ "backend" ]

View File

@ -3,4 +3,5 @@
[print_schema]
file = "src/schema.rs"
import_types = ["diesel::sql_types::*", "jirs_data::sql::*"]
with_docs = true

View File

@ -1,4 +1,6 @@
DROP TYPE IF EXISTS ProjectCategory CASCADE;
DROP TYPE IF EXISTS "ProjectCategoryType" CASCADE;
DROP TYPE IF EXISTS "IssuePriorityType" CASCADE;
DROP TYPE IF EXISTS "IssueTypeType" CASCADE;
DROP TABLE IF EXISTS projects CASCADE;

View File

@ -1,11 +1,25 @@
CREATE EXTENSION "uuid-ossp";
CREATE TYPE ProjectCategory as ENUM (
CREATE TYPE "ProjectCategoryType" as ENUM (
'software',
'marketing',
'business'
);
CREATE TYPE "IssuePriorityType" as ENUM (
'highest',
'high',
'medium',
'low',
'lowest'
);
CREATE TYPE "IssueTypeType" AS ENUM (
'task',
'bug',
'story'
);
CREATE TABLE projects (
id serial primary key not null,
name text not null,
@ -29,9 +43,9 @@ CREATE TABLE users (
CREATE TABLE issues (
id serial primary key not null,
title text not null,
issue_type text not null,
issue_type "IssueTypeType" not null,
status text not null,
priority text not null,
priority "IssuePriorityType" not null,
list_position double precision not null default 0,
description text,
description_text text,
@ -61,4 +75,3 @@ CREATE TABLE tokens (
created_at timestamp not null default now(),
updated_at timestamp not null default now()
);

View File

@ -34,7 +34,7 @@ insert into issues(
), (
'Foo3',
'bug',
'inprogress',
'in_progress',
'low',
3,
'hello world 3',

View File

@ -4,6 +4,8 @@ use diesel::expression::sql_literal::sql;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use jirs_data::{IssuePriority, IssueType};
use crate::db::DbExecutor;
use crate::errors::ServiceErrors;
use crate::models::Issue;
@ -66,9 +68,9 @@ impl Handler<LoadProjectIssues> for DbExecutor {
pub struct UpdateIssue {
pub issue_id: i32,
pub title: Option<String>,
pub issue_type: Option<String>,
pub issue_type: Option<IssueType>,
pub status: Option<String>,
pub priority: Option<String>,
pub priority: Option<IssuePriority>,
pub list_position: Option<f64>,
pub description: Option<Option<String>>,
pub description_text: Option<Option<String>>,
@ -191,9 +193,9 @@ impl Handler<DeleteIssue> for DbExecutor {
#[derive(Serialize, Deserialize)]
pub struct CreateIssue {
pub title: String,
pub issue_type: String,
pub issue_type: IssueType,
pub status: String,
pub priority: String,
pub priority: IssuePriority,
pub description: Option<String>,
pub description_text: Option<String>,
pub estimate: Option<i32>,

View File

@ -2,7 +2,8 @@ use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use jirs_data::IssueStatus;
use jirs_data::sql::*;
use jirs_data::{IssuePriority, IssueStatus, IssueType};
use crate::schema::*;
@ -47,9 +48,9 @@ pub struct Issue {
pub id: i32,
pub title: String,
#[serde(rename = "type")]
pub issue_type: String,
pub issue_type: IssueType,
pub status: String,
pub priority: String,
pub priority: IssuePriority,
pub list_position: f64,
pub description: Option<String>,
pub description_text: Option<String>,
@ -121,9 +122,9 @@ impl Into<jirs_data::FullIssue> for Issue {
pub struct CreateIssueForm {
pub title: String,
#[serde(rename = "type")]
pub issue_type: String,
pub issue_type: IssueType,
pub status: String,
pub priority: String,
pub priority: IssuePriority,
pub list_position: f64,
pub description: Option<String>,
pub description_text: Option<String>,

View File

@ -1,75 +1,341 @@
table! {
use diesel::sql_types::*;
use jirs_data::sql::*;
/// Representation of the `comments` table.
///
/// (Automatically generated by Diesel.)
comments (id) {
/// The `id` column of the `comments` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
id -> Int4,
/// The `body` column of the `comments` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
body -> Text,
/// The `user_id` column of the `comments` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
user_id -> Int4,
/// The `issue_id` column of the `comments` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
issue_id -> Int4,
/// The `created_at` column of the `comments` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
created_at -> Timestamp,
/// The `updated_at` column of the `comments` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
updated_at -> Timestamp,
}
}
table! {
use diesel::sql_types::*;
use jirs_data::sql::*;
/// Representation of the `issue_assignees` table.
///
/// (Automatically generated by Diesel.)
issue_assignees (id) {
/// The `id` column of the `issue_assignees` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
id -> Int4,
/// The `issue_id` column of the `issue_assignees` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
issue_id -> Int4,
/// The `user_id` column of the `issue_assignees` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
user_id -> Int4,
/// The `created_at` column of the `issue_assignees` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
created_at -> Timestamp,
/// The `updated_at` column of the `issue_assignees` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
updated_at -> Timestamp,
}
}
table! {
use diesel::sql_types::*;
use jirs_data::sql::*;
/// Representation of the `issues` table.
///
/// (Automatically generated by Diesel.)
issues (id) {
/// The `id` column of the `issues` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
id -> Int4,
/// The `title` column of the `issues` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
title -> Text,
issue_type -> Text,
/// The `issue_type` column of the `issues` table.
///
/// Its SQL type is `IssueTypeType`.
///
/// (Automatically generated by Diesel.)
issue_type -> IssueTypeType,
/// The `status` column of the `issues` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
status -> Text,
priority -> Text,
/// The `priority` column of the `issues` table.
///
/// Its SQL type is `IssuePriorityType`.
///
/// (Automatically generated by Diesel.)
priority -> IssuePriorityType,
/// The `list_position` column of the `issues` table.
///
/// Its SQL type is `Float8`.
///
/// (Automatically generated by Diesel.)
list_position -> Float8,
/// The `description` column of the `issues` table.
///
/// Its SQL type is `Nullable<Text>`.
///
/// (Automatically generated by Diesel.)
description -> Nullable<Text>,
/// The `description_text` column of the `issues` table.
///
/// Its SQL type is `Nullable<Text>`.
///
/// (Automatically generated by Diesel.)
description_text -> Nullable<Text>,
/// The `estimate` column of the `issues` table.
///
/// Its SQL type is `Nullable<Int4>`.
///
/// (Automatically generated by Diesel.)
estimate -> Nullable<Int4>,
/// The `time_spent` column of the `issues` table.
///
/// Its SQL type is `Nullable<Int4>`.
///
/// (Automatically generated by Diesel.)
time_spent -> Nullable<Int4>,
/// The `time_remaining` column of the `issues` table.
///
/// Its SQL type is `Nullable<Int4>`.
///
/// (Automatically generated by Diesel.)
time_remaining -> Nullable<Int4>,
/// The `reporter_id` column of the `issues` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
reporter_id -> Int4,
/// The `project_id` column of the `issues` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
project_id -> Int4,
/// The `created_at` column of the `issues` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
created_at -> Timestamp,
/// The `updated_at` column of the `issues` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
updated_at -> Timestamp,
}
}
table! {
use diesel::sql_types::*;
use jirs_data::sql::*;
/// Representation of the `projects` table.
///
/// (Automatically generated by Diesel.)
projects (id) {
/// The `id` column of the `projects` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
id -> Int4,
/// The `name` column of the `projects` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
name -> Text,
/// The `url` column of the `projects` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
url -> Text,
/// The `description` column of the `projects` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
description -> Text,
/// The `category` column of the `projects` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
category -> Text,
/// The `created_at` column of the `projects` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
created_at -> Timestamp,
/// The `updated_at` column of the `projects` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
updated_at -> Timestamp,
}
}
table! {
use diesel::sql_types::*;
use jirs_data::sql::*;
/// Representation of the `tokens` table.
///
/// (Automatically generated by Diesel.)
tokens (id) {
/// The `id` column of the `tokens` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
id -> Int4,
/// The `user_id` column of the `tokens` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
user_id -> Int4,
/// The `access_token` column of the `tokens` table.
///
/// Its SQL type is `Uuid`.
///
/// (Automatically generated by Diesel.)
access_token -> Uuid,
/// The `refresh_token` column of the `tokens` table.
///
/// Its SQL type is `Uuid`.
///
/// (Automatically generated by Diesel.)
refresh_token -> Uuid,
/// The `created_at` column of the `tokens` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
created_at -> Timestamp,
/// The `updated_at` column of the `tokens` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
updated_at -> Timestamp,
}
}
table! {
use diesel::sql_types::*;
use jirs_data::sql::*;
/// Representation of the `users` table.
///
/// (Automatically generated by Diesel.)
users (id) {
/// The `id` column of the `users` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
id -> Int4,
/// The `name` column of the `users` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
name -> Text,
/// The `email` column of the `users` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
email -> Text,
/// The `avatar_url` column of the `users` table.
///
/// Its SQL type is `Nullable<Text>`.
///
/// (Automatically generated by Diesel.)
avatar_url -> Nullable<Text>,
/// The `project_id` column of the `users` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
project_id -> Int4,
/// The `created_at` column of the `users` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
created_at -> Timestamp,
/// The `updated_at` column of the `users` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
updated_at -> Timestamp,
}
}
@ -83,4 +349,11 @@ joinable!(issues -> users (reporter_id));
joinable!(tokens -> users (user_id));
joinable!(users -> projects (project_id));
allow_tables_to_appear_in_same_query!(comments, issue_assignees, issues, projects, tokens, users,);
allow_tables_to_appear_in_same_query!(
comments,
issue_assignees,
issues,
projects,
tokens,
users,
);