Initial commit
This commit is contained in:
commit
bc2d8a3963
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
1890
Cargo.lock
generated
Normal file
1890
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"./jirs-cli",
|
||||||
|
"./jirs-server",
|
||||||
|
"./jirs-client"
|
||||||
|
]
|
||||||
|
|
8
docker-compose.yml
Normal file
8
docker-compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
version: '3.0'
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:latest
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
11
jirs-cli/Cargo.toml
Normal file
11
jirs-cli/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "jirs-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "jirs"
|
||||||
|
path = "./src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
1
jirs-cli/src/main.rs
Normal file
1
jirs-cli/src/main.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub fn main() {}
|
11
jirs-client/Cargo.toml
Normal file
11
jirs-client/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "jirs-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "jirs_client"
|
||||||
|
path = "./src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
1
jirs-client/src/lib.rs
Normal file
1
jirs-client/src/lib.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
42
jirs-server/Cargo.toml
Normal file
42
jirs-server/Cargo.toml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[package]
|
||||||
|
name = "jirs-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "jirs_server"
|
||||||
|
path = "./src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "*", features = ["derive"] }
|
||||||
|
actix = { version = "*" }
|
||||||
|
actix-web = { version = "*" }
|
||||||
|
actix-cors = { version = "*" }
|
||||||
|
actix-service = { version = "*" }
|
||||||
|
actix-rt = "1"
|
||||||
|
dotenv = { version = "*" }
|
||||||
|
byteorder = "1.0"
|
||||||
|
chrono = { version = "0.4", features = [ "serde" ] }
|
||||||
|
libc = { version = "0.2.0" }
|
||||||
|
pq-sys = { version = ">=0.3.0, <0.5.0" }
|
||||||
|
quickcheck = { version = "0.4" }
|
||||||
|
serde_json = { version = ">=0.8.0, <2.0" }
|
||||||
|
time = { version = "0.1" }
|
||||||
|
url = { version = "2.1.0" }
|
||||||
|
percent-encoding = { version = "2.1.0" }
|
||||||
|
uuid = { version = ">=0.7.0, <0.9.0", features = ["serde"] }
|
||||||
|
ipnetwork = { version = ">=0.12.2, <0.17.0" }
|
||||||
|
num-bigint = { version = ">=0.1.41, <0.3" }
|
||||||
|
num-traits = { version = "0.2" }
|
||||||
|
num-integer = { version = "0.1.32" }
|
||||||
|
bigdecimal = { version = ">= 0.0.10, <= 0.1.0" }
|
||||||
|
bitflags = { version = "1.0" }
|
||||||
|
r2d2 = { version = ">= 0.8, < 0.9" }
|
||||||
|
env_logger = "0.7"
|
||||||
|
futures = { version = "*" }
|
||||||
|
|
||||||
|
[dependencies.diesel]
|
||||||
|
version = "1.4.4"
|
||||||
|
features = [ "unstable", "postgres", "numeric", "extras", "uuidv07" ]
|
||||||
|
|
6
jirs-server/diesel.toml
Normal file
6
jirs-server/diesel.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
||||||
|
|
0
jirs-server/migrations/.gitkeep
Normal file
0
jirs-server/migrations/.gitkeep
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||||
|
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
@ -0,0 +1,36 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Sets up a trigger for the given table to automatically set a column called
|
||||||
|
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||||
|
-- in the modified columns)
|
||||||
|
--
|
||||||
|
-- # Example
|
||||||
|
--
|
||||||
|
-- ```sql
|
||||||
|
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||||
|
--
|
||||||
|
-- SELECT diesel_manage_updated_at('users');
|
||||||
|
-- ```
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||||
|
BEGIN
|
||||||
|
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
IF (
|
||||||
|
NEW IS DISTINCT FROM OLD AND
|
||||||
|
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||||
|
) THEN
|
||||||
|
NEW.updated_at := current_timestamp;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
14
jirs-server/migrations/2020-03-25-161803_initial/down.sql
Normal file
14
jirs-server/migrations/2020-03-25-161803_initial/down.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
DROP TYPE IF EXISTS ProjectCategory CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS projects CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS users CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS issues CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS comments CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS tokens CASCADE;
|
||||||
|
|
||||||
|
DROP EXTENSION IF EXISTS "uuid-ossp";
|
||||||
|
|
64
jirs-server/migrations/2020-03-25-161803_initial/up.sql
Normal file
64
jirs-server/migrations/2020-03-25-161803_initial/up.sql
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
CREATE EXTENSION "uuid-ossp";
|
||||||
|
|
||||||
|
CREATE TYPE ProjectCategory as ENUM (
|
||||||
|
'software',
|
||||||
|
'marketing',
|
||||||
|
'business'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE projects (
|
||||||
|
id serial primary key not null,
|
||||||
|
name text not null,
|
||||||
|
url text not null default '',
|
||||||
|
description text not null default '',
|
||||||
|
category text not null default 'software',
|
||||||
|
created_at timestamp not null default now(),
|
||||||
|
updated_at timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
id serial primary key not null,
|
||||||
|
name text not null,
|
||||||
|
email text not null,
|
||||||
|
avatar_url text,
|
||||||
|
project_id integer not null references projects (id),
|
||||||
|
created_at timestamp not null default now(),
|
||||||
|
updated_at timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE issues (
|
||||||
|
id serial primary key not null,
|
||||||
|
title text not null,
|
||||||
|
issue_type text not null,
|
||||||
|
status text not null,
|
||||||
|
priority text not null,
|
||||||
|
list_position double precision not null default 0,
|
||||||
|
description text,
|
||||||
|
description_text text,
|
||||||
|
estimate integer,
|
||||||
|
time_spent integer,
|
||||||
|
time_remaining integer,
|
||||||
|
reporter_id integer not null references users (id),
|
||||||
|
project_id integer not null references projects (id),
|
||||||
|
created_at timestamp not null default now(),
|
||||||
|
updated_at timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE comments (
|
||||||
|
id serial primary key not null,
|
||||||
|
body text not null,
|
||||||
|
user_id integer not null references users (id),
|
||||||
|
issue_id integer not null references issues (id),
|
||||||
|
created_at timestamp not null default now(),
|
||||||
|
updated_at timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE tokens (
|
||||||
|
id serial primary key not null,
|
||||||
|
user_id integer not null references users (id),
|
||||||
|
access_token uuid not null,
|
||||||
|
refresh_token uuid not null,
|
||||||
|
created_at timestamp not null default now(),
|
||||||
|
updated_at timestamp not null default now()
|
||||||
|
);
|
||||||
|
|
4
jirs-server/migrations/seed.sql
Normal file
4
jirs-server/migrations/seed.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO projects (name) values ('initial');
|
||||||
|
INSERT INTO users (project_id, email, name) values (1, 'foo', 'bar');
|
||||||
|
INSERT INTO tokens (user_id, access_token, refresh_token) values (1, uuid_generate_v4(), uuid_generate_v4() );
|
||||||
|
|
34
jirs-server/src/db/authorize_user.rs
Normal file
34
jirs-server/src/db/authorize_user.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use crate::db::DbExecutor;
|
||||||
|
use crate::models::{Token, User};
|
||||||
|
use actix::{Handler, Message};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct AuthorizeUser {
|
||||||
|
pub access_token: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for AuthorizeUser {
|
||||||
|
type Result = Result<crate::models::User, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<AuthorizeUser> for DbExecutor {
|
||||||
|
type Result = Result<crate::models::User, String>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: AuthorizeUser, _: &mut Self::Context) -> Self::Result {
|
||||||
|
use crate::schema::tokens::dsl::{access_token, tokens};
|
||||||
|
use crate::schema::users::dsl::{id, users};
|
||||||
|
|
||||||
|
let conn: &PgConnection = &self.0.get().unwrap();
|
||||||
|
let token = tokens
|
||||||
|
.filter(access_token.eq(msg.access_token))
|
||||||
|
.first::<Token>(conn)
|
||||||
|
.map_err(|e| format!("{}", e))?;
|
||||||
|
let user = users
|
||||||
|
.filter(id.eq(token.user_id))
|
||||||
|
.first::<User>(conn)
|
||||||
|
.map_err(|e| format!("{}", e))?;
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
}
|
30
jirs-server/src/db/mod.rs
Normal file
30
jirs-server/src/db/mod.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use actix::{Actor, SyncContext};
|
||||||
|
use diesel::pg::PgConnection;
|
||||||
|
use diesel::r2d2::{self, ConnectionManager};
|
||||||
|
|
||||||
|
pub mod authorize_user;
|
||||||
|
|
||||||
|
type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;
|
||||||
|
|
||||||
|
pub struct DbExecutor(pub DbPool);
|
||||||
|
|
||||||
|
impl Actor for DbExecutor {
|
||||||
|
type Context = SyncContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbExecutor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(build_pool())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_pool() -> DbPool {
|
||||||
|
std::env::set_var("RUST_LOG", "actix_web=info,diesel=debug");
|
||||||
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
|
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL");
|
||||||
|
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
||||||
|
r2d2::Pool::builder()
|
||||||
|
.build(manager)
|
||||||
|
.expect("Failed to create pool.")
|
||||||
|
}
|
48
jirs-server/src/main.rs
Normal file
48
jirs-server/src/main.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate diesel;
|
||||||
|
|
||||||
|
use actix_cors::Cors;
|
||||||
|
use actix_web::{web, App, HttpServer};
|
||||||
|
|
||||||
|
pub mod db;
|
||||||
|
pub mod middleware;
|
||||||
|
pub mod models;
|
||||||
|
pub mod routes;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
|
#[actix_rt::main]
|
||||||
|
async fn main() -> Result<(), String> {
|
||||||
|
std::env::set_var("RUST_LOG", "actix_web=info,diesel=debug");
|
||||||
|
env_logger::init();
|
||||||
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
|
let db_addr = actix::SyncArbiter::start(4, || crate::db::DbExecutor::new());
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.wrap(actix_web::middleware::Logger::default())
|
||||||
|
.wrap(Cors::default())
|
||||||
|
.data(db_addr.clone())
|
||||||
|
.service(
|
||||||
|
web::scope("/issues")
|
||||||
|
.wrap(crate::middleware::authorize::Authorize::default())
|
||||||
|
.service(crate::routes::issues::project_issues)
|
||||||
|
.service(crate::routes::issues::issue_with_users_and_omments)
|
||||||
|
.service(crate::routes::issues::create)
|
||||||
|
.service(crate::routes::issues::update)
|
||||||
|
.service(crate::routes::issues::delete),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::scope("/comments")
|
||||||
|
.service(crate::routes::comments::create)
|
||||||
|
.service(crate::routes::comments::update)
|
||||||
|
.service(crate::routes::comments::delete),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:8080")
|
||||||
|
.map_err(|e| format!("{}", e))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
.expect("Server internal error");
|
||||||
|
Ok(())
|
||||||
|
}
|
115
jirs-server/src/middleware/authorize.rs
Normal file
115
jirs-server/src/middleware/authorize.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
use actix_service::{Service, Transform};
|
||||||
|
use actix_web::http::header::{self};
|
||||||
|
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error, HttpResponse};
|
||||||
|
use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
type Db = actix_web::web::Data<actix::Addr<crate::db::DbExecutor>>;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ErrorResponse {
|
||||||
|
errors: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Authorize;
|
||||||
|
|
||||||
|
impl<S, B> Transform<S> for Authorize
|
||||||
|
where
|
||||||
|
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Request = ServiceRequest;
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = Error;
|
||||||
|
type InitError = ();
|
||||||
|
type Transform = AuthorizeMiddleware<S>;
|
||||||
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
ok(AuthorizeMiddleware { service })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AuthorizeMiddleware<S> {
|
||||||
|
service: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> Service for AuthorizeMiddleware<S>
|
||||||
|
where
|
||||||
|
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Request = ServiceRequest;
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Either<
|
||||||
|
Ready<Result<Self::Response, Error>>,
|
||||||
|
LocalBoxFuture<'static, Result<Self::Response, Error>>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.service.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
|
let header_value = req.headers().get(&header::AUTHORIZATION);
|
||||||
|
let access_token =
|
||||||
|
match header_value.and_then(|s| parse_bearer(s.to_str().unwrap_or_default())) {
|
||||||
|
Some(token) => token,
|
||||||
|
_ => {
|
||||||
|
println!("No access token found");
|
||||||
|
let res = HttpResponse::Unauthorized().body("").into_body();
|
||||||
|
return Either::Left(ok(req.into_response(res)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let db_executor: Db = match req.app_data() {
|
||||||
|
Some(d) => d,
|
||||||
|
_ => {
|
||||||
|
let response = ErrorResponse {
|
||||||
|
errors: vec!["Database connection failed".to_string()],
|
||||||
|
};
|
||||||
|
let res = HttpResponse::InternalServerError()
|
||||||
|
.json(response)
|
||||||
|
.into_body();
|
||||||
|
return Either::Left(ok(req.into_response(res)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
|
||||||
|
Either::Right(
|
||||||
|
async move {
|
||||||
|
match check_token(access_token, db_executor).await {
|
||||||
|
Some(user) => (),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
fut.await
|
||||||
|
}
|
||||||
|
.boxed_local(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_token(access_token: uuid::Uuid, db_executor: Db) -> Option<crate::models::User> {
|
||||||
|
use crate::db::authorize_user::AuthorizeUser;
|
||||||
|
match db_executor.send(AuthorizeUser { access_token }).await {
|
||||||
|
Ok(Ok(user)) => Some(user),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_bearer(header: &str) -> Option<uuid::Uuid> {
|
||||||
|
if !header.starts_with("Bearer ") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (_bearer, token) = header.split_at(7);
|
||||||
|
match uuid::Uuid::parse_str(token) {
|
||||||
|
Ok(u) => Some(u),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
1
jirs-server/src/middleware/mod.rs
Normal file
1
jirs-server/src/middleware/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod authorize;
|
116
jirs-server/src/models.rs
Normal file
116
jirs-server/src/models.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
use crate::schema::*;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
|
pub struct Comment {
|
||||||
|
pub id: i32,
|
||||||
|
pub body: String,
|
||||||
|
pub user_id: Option<i32>,
|
||||||
|
pub issue_id: Option<i32>,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
||||||
|
#[table_name = "comments"]
|
||||||
|
pub struct CommentForm {
|
||||||
|
pub body: String,
|
||||||
|
pub user_id: Option<i32>,
|
||||||
|
pub issue_id: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
|
pub struct Issue {
|
||||||
|
pub id: i32,
|
||||||
|
pub title: String,
|
||||||
|
pub issue_type: String,
|
||||||
|
pub status: String,
|
||||||
|
pub priority: String,
|
||||||
|
pub list_position: f64,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub description_text: Option<String>,
|
||||||
|
pub estimate: Option<i32>,
|
||||||
|
pub time_spent: Option<i32>,
|
||||||
|
pub time_remaining: Option<i32>,
|
||||||
|
pub reporter_id: Option<i32>,
|
||||||
|
pub project_id: Option<i32>,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
||||||
|
#[table_name = "issues"]
|
||||||
|
pub struct IssueForm {
|
||||||
|
pub title: String,
|
||||||
|
pub issue_type: String,
|
||||||
|
pub status: String,
|
||||||
|
pub priority: String,
|
||||||
|
pub list_position: f64,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub description_text: Option<String>,
|
||||||
|
pub estimate: Option<i32>,
|
||||||
|
pub time_spent: Option<i32>,
|
||||||
|
pub time_remaining: Option<i32>,
|
||||||
|
pub reporter_id: Option<i32>,
|
||||||
|
pub project_id: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
|
pub struct Project {
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub url: String,
|
||||||
|
pub description: String,
|
||||||
|
pub category: String,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
||||||
|
#[table_name = "projects"]
|
||||||
|
pub struct ProjectForm {
|
||||||
|
pub name: String,
|
||||||
|
pub url: String,
|
||||||
|
pub description: String,
|
||||||
|
pub category: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub email: String,
|
||||||
|
pub avatar_url: Option<String>,
|
||||||
|
pub project_id: i32,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
||||||
|
#[table_name = "users"]
|
||||||
|
pub struct UserForm {
|
||||||
|
pub name: String,
|
||||||
|
pub email: String,
|
||||||
|
pub avatar_url: Option<String>,
|
||||||
|
pub project_id: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
|
pub struct Token {
|
||||||
|
pub id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
pub access_token: Uuid,
|
||||||
|
pub refresh_token: Uuid,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
||||||
|
#[table_name = "tokens"]
|
||||||
|
pub struct TokenForm {
|
||||||
|
pub user_id: i32,
|
||||||
|
pub access_token: Uuid,
|
||||||
|
pub refresh_token: Uuid,
|
||||||
|
}
|
18
jirs-server/src/routes/comments.rs
Normal file
18
jirs-server/src/routes/comments.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use actix_web::{delete, post, put, HttpResponse};
|
||||||
|
|
||||||
|
#[post("/")]
|
||||||
|
pub async fn create() -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body("<!DOCTYPE html><html><head><title>Issues</title></head><body>Foo</body></html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/<id>")]
|
||||||
|
pub async fn update() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().content_type("text/html").body("")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/<id>")]
|
||||||
|
pub async fn delete() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().content_type("text/html").body("")
|
||||||
|
}
|
28
jirs-server/src/routes/issues.rs
Normal file
28
jirs-server/src/routes/issues.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use actix_web::{delete, get, post, put, HttpResponse};
|
||||||
|
|
||||||
|
#[get("")]
|
||||||
|
pub async fn project_issues() -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body("<!DOCTYPE html><html><head><title>Issues</title></head><body>Foo</body></html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/<id>")]
|
||||||
|
pub async fn issue_with_users_and_omments() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().content_type("text/html").body("")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/")]
|
||||||
|
pub async fn create() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().content_type("text/html").body("")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/<id>")]
|
||||||
|
pub async fn update() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().content_type("text/html").body("")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/<id>")]
|
||||||
|
pub async fn delete() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().content_type("text/html").body("")
|
||||||
|
}
|
4
jirs-server/src/routes/mod.rs
Normal file
4
jirs-server/src/routes/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod comments;
|
||||||
|
pub mod issues;
|
||||||
|
pub mod projects;
|
||||||
|
pub mod users;
|
1
jirs-server/src/routes/projects.rs
Normal file
1
jirs-server/src/routes/projects.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
6
jirs-server/src/routes/users.rs
Normal file
6
jirs-server/src/routes/users.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use actix_web::{get, HttpResponse};
|
||||||
|
|
||||||
|
#[get("/currentUser")]
|
||||||
|
pub async fn current_user() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().content_type("text/html").body("")
|
||||||
|
}
|
74
jirs-server/src/schema.rs
Normal file
74
jirs-server/src/schema.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
table! {
|
||||||
|
comments (id) {
|
||||||
|
id -> Int4,
|
||||||
|
body -> Text,
|
||||||
|
user_id -> Int4,
|
||||||
|
issue_id -> Int4,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
issues (id) {
|
||||||
|
id -> Int4,
|
||||||
|
title -> Text,
|
||||||
|
issue_type -> Text,
|
||||||
|
status -> Text,
|
||||||
|
priority -> Text,
|
||||||
|
list_position -> Float8,
|
||||||
|
description -> Nullable<Text>,
|
||||||
|
description_text -> Nullable<Text>,
|
||||||
|
estimate -> Nullable<Int4>,
|
||||||
|
time_spent -> Nullable<Int4>,
|
||||||
|
time_remaining -> Nullable<Int4>,
|
||||||
|
reporter_id -> Int4,
|
||||||
|
project_id -> Int4,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
projects (id) {
|
||||||
|
id -> Int4,
|
||||||
|
name -> Text,
|
||||||
|
url -> Text,
|
||||||
|
description -> Text,
|
||||||
|
category -> Text,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
tokens (id) {
|
||||||
|
id -> Int4,
|
||||||
|
user_id -> Int4,
|
||||||
|
access_token -> Uuid,
|
||||||
|
refresh_token -> Uuid,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
users (id) {
|
||||||
|
id -> Int4,
|
||||||
|
name -> Text,
|
||||||
|
email -> Text,
|
||||||
|
avatar_url -> Nullable<Text>,
|
||||||
|
project_id -> Int4,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
joinable!(comments -> issues (issue_id));
|
||||||
|
joinable!(comments -> users (user_id));
|
||||||
|
joinable!(issues -> projects (project_id));
|
||||||
|
joinable!(issues -> users (reporter_id));
|
||||||
|
joinable!(tokens -> users (user_id));
|
||||||
|
joinable!(users -> projects (project_id));
|
||||||
|
|
||||||
|
allow_tables_to_appear_in_same_query!(comments, issues, projects, tokens, users,);
|
Loading…
Reference in New Issue
Block a user