Commit 31227178 authored by Ibadullina Inabat's avatar Ibadullina Inabat

Merge branch 'development' of…

Merge branch 'development' of ssh://git.attractor-school.com:30022/apollo64/crm-team-one into task-73-feature/create_project_by_modal_front
parents 8a1dd26e ef9b5054
...@@ -3,6 +3,7 @@ import {User} from './models/User'; ...@@ -3,6 +3,7 @@ import {User} from './models/User';
import {Task} from './models/Task'; import {Task} from './models/Task';
import { Project } from "./models/Project"; import { Project } from "./models/Project";
import { Member } from "./models/Member"; import { Member } from "./models/Member";
import { DateTimeTask } from "./models/DateTimeTask";
export const myDataSource = new DataSource({ export const myDataSource = new DataSource({
type: "postgres", type: "postgres",
...@@ -11,7 +12,7 @@ export const myDataSource = new DataSource({ ...@@ -11,7 +12,7 @@ export const myDataSource = new DataSource({
username: "pluser", username: "pluser",
password: "pluser", password: "pluser",
database: "planner", database: "planner",
entities: [User,Task,Project,Member], entities: [User,Task,Project,Member,DateTimeTask],
logging: true, logging: true,
synchronize: true, // in build switch to false synchronize: true, // in build switch to false
migrationsRun: false migrationsRun: false
......
...@@ -2,12 +2,37 @@ import { myDataSource } from "./app-data-source"; ...@@ -2,12 +2,37 @@ import { myDataSource } from "./app-data-source";
import { User, UserRole } from "./models/User"; import { User, UserRole } from "./models/User";
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import { Task } from "./models/Task"; import { priorityType, Task, taskFinishType } from "./models/Task";
import { Project } from "./models/Project"; import { Project } from "./models/Project";
import { Member, MemberRole } from "./models/Member"; import { Member, MemberRole } from "./models/Member";
import { DateTimeTask } from "./models/DateTimeTask";
function randomIntFromInterval(min:number, max:number) { function randomIntFromInterval(min:number, max:number) {
return Math.floor(Math.random() * (max - min + 1) + min) min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
let countMembers =0
let countRolesProject=0
const cycleThroughObject=(countKey:number,objectObserve:any):MemberRole=>{
let arrayOfKeys = Object.keys(objectObserve)
let keyOfObject = arrayOfKeys[countKey]
let valueOfKey = objectObserve[keyOfObject]
countKey++
if(countKey===arrayOfKeys.length-1){
countKey=0
}
return valueOfKey
}
const cycleArrayOfMembers=(countIndex:number, members:Member[]):Member=>{
let member = members[countIndex]
countIndex++
if (countIndex ===members.length-1){
countIndex=0
}
return member
} }
...@@ -19,25 +44,27 @@ const loadFixtures = async () => { ...@@ -19,25 +44,27 @@ const loadFixtures = async () => {
await repositoryMember.delete({}) await repositoryMember.delete({})
const repositoryTask = myDataSource.getRepository(Task); const repositoryTask = myDataSource.getRepository(Task);
await repositoryTask.delete({}); await repositoryTask.delete({});
const repositoryDateTimeTask = myDataSource.getRepository(DateTimeTask);
await repositoryDateTimeTask.delete({})
const repositoryProject = myDataSource.getRepository(Project); const repositoryProject = myDataSource.getRepository(Project);
await repositoryProject.delete({}) await repositoryProject.delete({})
const repositoryUser = myDataSource.getRepository(User); const repositoryUser = myDataSource.getRepository(User);
await repositoryUser.delete({}); await repositoryUser.delete({});
console.log('========================== ' + '\n' + 'Data Source has been cleared!' +'\n' + '==========================') console.log('========================== ' + '\n' + 'Data Source has been cleared!' +'\n' + '==========================')
const userRoles = [{role: UserRole.DIRECTOR}, {role: UserRole.SUPERUSER}, {role: UserRole.USER}, {role: UserRole.USER}]; const userRoles = [{role: UserRole.SUPERUSER}, {role: UserRole.USER}, {role: UserRole.USER},{role: UserRole.USER},];
const users = [] const users = []
for (let i = 0; i < 4; i++) { for (let i = 0; i < 3; i++) {
const name = faker.name.firstName() const name = faker.name.firstName()
const surname = faker.name.lastName() const surname = faker.name.lastName()
const displayName = name + ' ' + surname[0] + '.' const displayName = name + ' ' + surname[0] + '.'
const user = new User() const user = new User()
user.name = name; user.name = name;
user.surname = surname; user.surname = surname;
user.password = '12345qwert'; user.password = '123';
user.displayName= displayName; user.displayName= displayName;
user.phone = faker.phone.number('+77#########') user.phone = faker.phone.number('+77#########')
user.email = faker.internet.email(); user.email = 'a@a.a'+i;
user.role = userRoles[i].role; user.role = userRoles[i].role;
user.generateToken() user.generateToken()
await user.save(); await user.save();
...@@ -45,21 +72,29 @@ const loadFixtures = async () => { ...@@ -45,21 +72,29 @@ const loadFixtures = async () => {
} }
const tasks:Task[] = [] const tasks:Task[] = []
type taskFinishType = "opened" | "done" |"failed";
type priorityType = "A" | "B" |"C";
const priorities:priorityType[] = ["A", "B" , "C"] const priorities:priorityType[] = ["A", "B" , "C"]
const accomplish:taskFinishType[] = ["opened", "done" , "failed"] const accomplish:taskFinishType[] = ["opened", "done" , "failed"]
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
let dateOfMonth = randomIntFromInterval(20, 30)
let deadLineDateOfMonth = randomIntFromInterval(1, 10)
let startDateTime = new Date(2022, 10, dateOfMonth, randomIntFromInterval(16, 21), 0, 0);
let dueDateTime = new Date(2022, 10, dateOfMonth, randomIntFromInterval(22, 22), 0, 0);
let deadLine = new Date(2022, 11, deadLineDateOfMonth, 0, 0, 0);
const newDateTimeTask= new DateTimeTask()
newDateTimeTask.dateTimeStart=startDateTime;
newDateTimeTask.dateTimeDue=dueDateTime;
await newDateTimeTask.save()
if (i <= 15) { if (i <= 15) {
const newTask = new Task(); const newTask = new Task();
newTask.title = `Buy ${faker.commerce.productName()}`; newTask.title = `Buy ${faker.commerce.productName()}`;
newTask.description = faker.random.words(4); newTask.description = faker.random.words(4);
newTask.executor = faker.helpers.arrayElement(users);
newTask.dateTimeDue = faker.date.soon(randomIntFromInterval(1, 15));
newTask.dateTimeStart = faker.date.recent((randomIntFromInterval(0, 8)));
newTask.dateTimeDeadLine = faker.date.soon(randomIntFromInterval(1, 15));
newTask.dateTimeFactDeadLine = faker.date.soon(randomIntFromInterval(1, 15));
newTask.author = faker.helpers.arrayElement(users); newTask.author = faker.helpers.arrayElement(users);
newTask.executor = faker.helpers.arrayElement(users);
newTask.dateTimeTasks=[newDateTimeTask]
newTask.dateTimeDeadLine =deadLine;
newTask.dateTimeFactDeadLine = deadLine;
newTask.accomplish = faker.helpers.arrayElement(accomplish); newTask.accomplish = faker.helpers.arrayElement(accomplish);
newTask.priority = faker.helpers.arrayElement(priorities); newTask.priority = faker.helpers.arrayElement(priorities);
await newTask.save(); await newTask.save();
...@@ -78,36 +113,33 @@ const loadFixtures = async () => { ...@@ -78,36 +113,33 @@ const loadFixtures = async () => {
} }
const members:Member[]=[] const members:Member[]=[]
for (let i = 0; i < 5; i++) { for (let i = 0; i <5; i++) {
const newMember = new Member(); const newMember = new Member();
newMember.user = faker.helpers.arrayElement(users); newMember.user = faker.helpers.arrayElement(users);
newMember.roleProject=faker.helpers.objectValue(MemberRole); newMember.roleProject=cycleThroughObject(countRolesProject, MemberRole);
await newMember.save(); await newMember.save();
members.push(newMember) members.push(newMember)
} }
const projects:Project[] = [] const projects:Project[] = []
for (let i = 0; i < 5; i++) { for (let i = 0; i < 3; i++) {
const newProject = new Project(); const newProject = new Project();
newProject.title = `Project ${faker.random.words(1)}`; newProject.title = `Project ${faker.random.words(1)}`;
newProject.color = faker.random.words(1); newProject.color = faker.random.words(1);
newProject.members = faker.helpers.arrayElements(members,randomIntFromInterval(1, 3)) newProject.members = faker.helpers.arrayElements(members, 3);
newProject.tasks= faker.helpers.arrayElements(tasks, randomIntFromInterval(1, 3)); newProject.tasks= faker.helpers.arrayElements(tasks, randomIntFromInterval(2, 19));
await newProject.save(); await newProject.save();
projects.push(newProject) projects.push(newProject)
} }
console.log('========================== ' + '\n' + 'Fixtures done!' +'\n' + '==========================') console.log('========================== ' + '\n' + 'Fixtures done!' +'\n' + '==========================')
}) })
.catch((err) => { .catch((err) => {
console.error("Error during Data Source initialization:", err) console.error("Error during Data Source initialization:", err)
}) })
}; };
......
import express, { NextFunction, Request, Response, Router } from "express";
import { myDataSource } from "./app-data-source";
import { Task } from "./models/Task";
import { User } from "./models/User";
const dataSource = myDataSource;
/** Check if user with given token exists , return user */
export const auth = async(req: Request,res: Response, next:NextFunction):Promise<void | express.Response<Response>>=>{
const token = req.get('Authorization');
if(!token) return res.status(401).send({Message:'token not exists'})
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.token = :token", { token: token })
.getOne();
if (!user) return res.status(404).send({Message:'user not found'})
req.body={...req.body,user:user}
next()
};
/**Check if user with the given token is executor or author of task with the given Id(taskId) */
export const authAuthorOrExecutorOfTask = async(req: Request,res: Response, next:NextFunction):Promise<void | express.Response<Response>>=>{
const token = req.get('Authorization');
const {taskId} = req.body
if(!token) return res.status(401).send({Message:'token not exists'})
req.body={...req.body,executorStatus:false}
req.body={...req.body,authorStatus:false}
const executor = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.leftJoinAndSelect("user.tasks", "task")
.where("user.token = :token", { token: token })
.getOne();
console.log('executor', executor)
if (executor) {
req.body={...req.body,executorStatus:true}
}
const author = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.leftJoinAndSelect("user.createdTasks", "task")
.where("user.token = :token", { token: token })
.getOne();
console.log('author', author)
if (author) {
req.body={...req.body,authorStatus:true}
}
if(!author && !executor)return res.status(401).send({Message:'user is not authorized'})
next()
};
/**task finder by id, return one task */
export const taskFinderById = async (taskId:string):Promise<null | Task>=>{
const task = await dataSource
.getRepository(Task)
.findOne({
relations:{
executor:true,
author:true,
dateTimeTasks:true
},
where:{
id:taskId
}
})
return task
}
\ No newline at end of file
import {
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
BaseEntity,
ManyToOne,
} from 'typeorm';
import {Task} from './Task';
interface IDateTimeTask{
id: string;
createdAt: Date;
dateTimeStart: Date;
dateTimeDue: Date;
task: Task;
}
@Entity({name:"DateTimeTask"})
export class DateTimeTask extends BaseEntity implements IDateTimeTask{
@PrimaryGeneratedColumn("uuid")
id!: string;
@CreateDateColumn({ name: 'createdAt', type: Date, default: new Date() })
createdAt!: Date;
@CreateDateColumn({ name: 'dateTimeStart', type: Date, nullable:false })
dateTimeStart!: Date;
@CreateDateColumn({ name: 'dateTimeDue', type: Date, nullable:false })
dateTimeDue!: Date;
@ManyToOne(() => Task, (task: { dateTimeTasks: DateTimeTask[]; }) => task.dateTimeTasks,{cascade: true, onUpdate:'CASCADE',onDelete:'CASCADE',nullable:true})
task!: Task;
}
\ No newline at end of file
...@@ -5,9 +5,7 @@ import { ...@@ -5,9 +5,7 @@ import {
CreateDateColumn, CreateDateColumn,
BaseEntity, BaseEntity,
ManyToOne, ManyToOne,
ManyToMany,
OneToMany,
JoinTable
} from 'typeorm'; } from 'typeorm';
import {User} from './User'; import {User} from './User';
import {Project} from './Project'; import {Project} from './Project';
......
...@@ -19,13 +19,9 @@ import { ...@@ -19,13 +19,9 @@ import {
id: string; id: string;
title: string; title: string;
color: string; color: string;
// admin:User;
// workers:User[];
tasks:Task[]|null; tasks:Task[]|null;
createdAt: Date; createdAt: Date;
active:boolean; active:boolean;
// dateDue: Date| null;
// department:boolean;
members: Member[]; members: Member[];
} }
...@@ -47,22 +43,10 @@ import { ...@@ -47,22 +43,10 @@ import {
@Column({ name: 'active',type: 'boolean', default: true }) @Column({ name: 'active',type: 'boolean', default: true })
active!: boolean; active!: boolean;
// @Column({ name: 'dateDue', type: Date, default: null })
// dateDue!: Date| null;
// @Column({ name: 'department', type: Boolean,nullable:true ,default: false})
// department!: boolean;
// @ManyToOne(() => User, (user: { projects: Project[]; }) => user.projects,{eager : true})
// admin!: User;
@OneToMany(() => Task, (task: { project: Project; })=>task.project,{nullable:true}) @OneToMany(() => Task, (task: { project: Project; })=>task.project,{nullable:true})
tasks!:Task[]; tasks!:Task[];
@OneToMany(() => Member, (member: { project: Project; })=>member.project) @OneToMany(() => Member, (member: { project: Project; })=>member.project)
members!:Member[]; members!:Member[];
// @ManyToMany(() => User, (user: { projects: Project[]; }) => user.projects,{eager : true,cascade: true, onUpdate:'CASCADE',onDelete: 'CASCADE'})
// @JoinTable()
// workers!: User[];
} }
\ No newline at end of file
...@@ -6,27 +6,30 @@ import { ...@@ -6,27 +6,30 @@ import {
BaseEntity, BaseEntity,
ManyToOne, ManyToOne,
OneToOne, OneToOne,
JoinTable JoinTable,
OneToMany
} from 'typeorm'; } from 'typeorm';
import {User} from './User'; import {User} from './User';
import {Project} from './Project'; import {Project} from './Project';
import { DateTimeTask } from './DateTimeTask';
type taskFinishType = "opened" | "done" |"failed"; export type taskFinishType = "opened"| "progress" | "done" |"failed";
type priorityType = "A" | "B" |"C"; export type priorityType = "A" | "B" |"C";
interface ITask{ interface ITask{
id: string; id: string;
title: string; title: string;
description: string; description: string;
note: string;
createdAt: Date; createdAt: Date;
dateTimeStart:Date| null;
dateTimeDue:Date| null;
dateTimeDeadLine: Date| null; dateTimeDeadLine: Date| null;
dateTimeFactDeadLine: Date| null; dateTimeFactDeadLine: Date| null;
accomplish: taskFinishType; accomplish: taskFinishType;
priority: priorityType | null; priority: priorityType | null;
archive:boolean,
author: User; author: User;
project:Project|null; project:Project|null;
dateTimeTasks:DateTimeTask[]|null;
executor:User; executor:User;
} }
...@@ -38,16 +41,16 @@ import { ...@@ -38,16 +41,16 @@ import {
title!: string title!: string
@Column({ name: 'description', type: 'varchar', length:50,nullable: true }) @Column({ name: 'description', type: 'varchar', length:50,nullable: true })
description!: string description!: string
@Column({ name: 'note', type: 'varchar', length:100,nullable: true })
note!: string
@CreateDateColumn({ name: 'created_at', type: Date, default: new Date() }) @CreateDateColumn({ name: 'created_at', type: Date, default: new Date() })
createdAt!: Date; createdAt!: Date;
@Column({ name: 'dateTimeStart', type: Date,nullable: true })
dateTimeStart!: Date ;
@Column({ name: 'dateTimeDue', type: Date,nullable: true })
dateTimeDue!: Date ;
@Column({ name: 'dateTimeDeadLine', type: Date,nullable: true }) @Column({ name: 'dateTimeDeadLine', type: Date,nullable: true })
dateTimeDeadLine!: Date; dateTimeDeadLine!: Date;
@Column({ name: 'dateTimeFactDeadLine', type: Date,nullable: true }) @Column({ name: 'dateTimeFactDeadLine', type: Date,nullable: true })
dateTimeFactDeadLine!: Date; dateTimeFactDeadLine!: Date;
@Column({ name: 'archive', type: 'varchar', length:50,nullable: false, default:false })
archive!: boolean
@Column({ @Column({
type: "enum", type: "enum",
...@@ -76,4 +79,7 @@ import { ...@@ -76,4 +79,7 @@ import {
@ManyToOne(()=>Project,(project:{tasks: Task[]}) => project.tasks,{eager : true,nullable: true,onUpdate:'CASCADE'}) @ManyToOne(()=>Project,(project:{tasks: Task[]}) => project.tasks,{eager : true,nullable: true,onUpdate:'CASCADE'})
project!: Project; project!: Project;
@OneToMany(() => DateTimeTask, (dateTimeTask: { task: Task }) => dateTimeTask.task,{eager : true,nullable: true,onUpdate:'CASCADE'})
dateTimeTasks!: DateTimeTask[];
} }
...@@ -16,7 +16,6 @@ import { Exclude, instanceToPlain } from "class-transformer"; ...@@ -16,7 +16,6 @@ import { Exclude, instanceToPlain } from "class-transformer";
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
import {nanoid} from 'nanoid'; import {nanoid} from 'nanoid';
import {Task} from './Task'; import {Task} from './Task';
import {Project} from './Project';
import {Member} from './Member'; import {Member} from './Member';
const SALT_WORK_FACTOR= 10; const SALT_WORK_FACTOR= 10;
...@@ -35,9 +34,7 @@ interface IUser { ...@@ -35,9 +34,7 @@ interface IUser {
token: string; token: string;
createdAt: Date; createdAt: Date;
createdTasks:Task[]; createdTasks:Task[];
// workerInProjects:Project[]; members: Member[];
// adminInProjects:Project[];
membership: Member[];
} }
...@@ -88,7 +85,7 @@ export class User extends BaseEntity implements IUser { ...@@ -88,7 +85,7 @@ export class User extends BaseEntity implements IUser {
@OneToMany(() => Member, (member: { user: User }) => member.user) @OneToMany(() => Member, (member: { user: User }) => member.user)
membership!: Member[]; members!: Member[];
// @ManyToMany(() => Project,(project: { user: User }) => project.user) // @ManyToMany(() => Project,(project: { user: User }) => project.user)
// @JoinTable() // @JoinTable()
......
import express,{Router, Request, Response,NextFunction } from 'express';
import {Task} from '../models/Task';
import {myDataSource} from '../app-data-source';
import { User } from '../models/User';
import { Member } from '../models/Member';
import { In } from 'typeorm';
import { DateTimeTask } from '../models/DateTimeTask';
import { auth, authAuthorOrExecutorOfTask } from '../helpers';
const router:Router = express.Router();
const dataSource = myDataSource;
/**task finder by id, return one task */
const taskFinderById = async (taskId:string):Promise<null | Task>=>{
const task = await dataSource
.getRepository(Task)
.findOne({
relations:{
executor:true,
author:true,
dateTimeTasks:true
},
where:{
id:taskId
}
})
return task
}
/** make copy of task in calendar view */
router.post("/make-copy",authAuthorOrExecutorOfTask, async(req:Request, res:Response):Promise<Response>=>{
const {executorStatus,taskId,start, due} = req.body
if (executorStatus){
const newDateTimeTask = new DateTimeTask();
newDateTimeTask.dateTimeStart = start
newDateTimeTask.dateTimeDue = due
newDateTimeTask.task = taskId
await newDateTimeTask.save()
const task = taskFinderById(taskId)
return res.send({task})
}
return res.send({message :"Something wrong in make-copy router"})
} )
/** change date time of copy of task in calendar view */
router.put("change-copy", authAuthorOrExecutorOfTask, async(req:Request, res: Response):Promise<Response>=>{
const {executorStatus,dateTimeTaskId,taskId, start, due} = req.body
if (executorStatus){
const dateTimeTask = await dataSource
.createQueryBuilder()
.select('dateTikeTask')
.from(DateTimeTask,'dateTimeTask')
.where("dateTimeTask.id = :dateTimeTaskId",{dateTimeTaskId})
.getOne()
if(!dateTimeTask) return res.send({message:"such dateTimeTask does not exists"})
dateTimeTask.dateTimeStart=start
dateTimeTask.dateTimeDue=due
await dateTimeTask.save()
const task = taskFinderById(taskId)
return res.send({task})
}
return res.send({message :"Something wrong in make-copy router"})
})
export default router;
import express,{Router, Request, Response} from 'express';
import {myDataSource} from '../app-data-source';
import { Member } from "../models/Member";
const router:Router = express.Router();
const dataSource = myDataSource;
...@@ -5,6 +5,7 @@ import { User } from '../models/User'; ...@@ -5,6 +5,7 @@ import { User } from '../models/User';
import { Member, MemberRole } from '../models/Member'; import { Member, MemberRole } from '../models/Member';
import { userInfo } from 'os'; import { userInfo } from 'os';
import { Task } from '../models/Task'; import { Task } from '../models/Task';
import { auth } from '../helpers';
const router:Router = express.Router(); const router:Router = express.Router();
const dataSource = myDataSource; const dataSource = myDataSource;
...@@ -14,18 +15,9 @@ router.get('/',async (req:Request, res:Response): Promise<Response>=> { ...@@ -14,18 +15,9 @@ router.get('/',async (req:Request, res:Response): Promise<Response>=> {
const projects:Project[] = await dataSource.manager.find(Project) const projects:Project[] = await dataSource.manager.find(Project)
return res.send({projects}) return res.send({projects})
}) })
/**get projects were user is member, by user token */
router.get('/my',async (req:Request, res:Response): Promise<Response>=> { router.get('/my',auth, async (req:Request, res:Response): Promise<Response>=> {
const token = req.get('Authorization'); const user = req.body.user
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.token = :token", { token: token })
.getOne();
if(!user) return res.status(404).send({Message:'user not found'})
const rawProjects = await dataSource const rawProjects = await dataSource
.createQueryBuilder() .createQueryBuilder()
.from(Project, "project") .from(Project, "project")
...@@ -34,18 +26,14 @@ router.get('/my',async (req:Request, res:Response): Promise<Response>=> { ...@@ -34,18 +26,14 @@ router.get('/my',async (req:Request, res:Response): Promise<Response>=> {
.loadRelationCountAndMap('project.tasks', 'project.tasks') .loadRelationCountAndMap('project.tasks', 'project.tasks')
.leftJoinAndSelect('member.user', 'user' ) .leftJoinAndSelect('member.user', 'user' )
.where('member.userId = :userId',{userId:user.id}) .where('member.userId = :userId',{userId:user.id})
// .where("project.id = :id", { id: req.params.id })
// .select(["*","members"])
// .select("*")
.getMany() .getMany()
const projectIds = [] const projectIds = []
if (rawProjects.length>0){ if (rawProjects.length>0){
for(let project of rawProjects){ for(let project of rawProjects){
projectIds.push(project.id projectIds.push(project.id
) )
} }
} }
console.log('projectIds', projectIds)
const projects = await dataSource const projects = await dataSource
.createQueryBuilder() .createQueryBuilder()
.from(Project, "project") .from(Project, "project")
...@@ -55,22 +43,14 @@ router.get('/my',async (req:Request, res:Response): Promise<Response>=> { ...@@ -55,22 +43,14 @@ router.get('/my',async (req:Request, res:Response): Promise<Response>=> {
.leftJoinAndSelect('member.user', 'user') .leftJoinAndSelect('member.user', 'user')
.where('project.id IN(:...projectIds)', {projectIds}) .where('project.id IN(:...projectIds)', {projectIds})
.getMany() .getMany()
// const projects:Project[] = await dataSource.manager.find(Project)
return res.send({projects}) return res.send({projects})
}) })
/**create new project */
router.post('/', async (req:Request, res:Response): Promise<Response> => { router.post('/',auth, async (req:Request, res:Response): Promise<Response> => {
if (!req.body) return res.status(400).send({Message:'problem in incoming req.body'}) if (!req.body) return res.status(400).send({Message:'problem in incoming req.body'})
const token = req.get('Authorization'); const {user, title,color}= req.body;
const {title,color}= req.body;
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.token = :token", { token: token })
.getOne();
if(!user) return res.status(404).send({Message:'user not found'})
const member:Member = new Member; const member:Member = new Member;
member.user= user; member.user= user;
member.roleProject= MemberRole.ADMIN; member.roleProject= MemberRole.ADMIN;
...@@ -82,7 +62,7 @@ router.post('/', async (req:Request, res:Response): Promise<Response> => { ...@@ -82,7 +62,7 @@ router.post('/', async (req:Request, res:Response): Promise<Response> => {
await project.save() await project.save()
return res.send({project}) return res.send({project})
}) })
/**get project with all FK & tasks with all FK, by project ID */
router.get("/:id",async (req:Request, res:Response): Promise<Response> => { router.get("/:id",async (req:Request, res:Response): Promise<Response> => {
const project = await dataSource const project = await dataSource
.createQueryBuilder() .createQueryBuilder()
...@@ -110,7 +90,7 @@ router.get("/:id",async (req:Request, res:Response): Promise<Response> => { ...@@ -110,7 +90,7 @@ router.get("/:id",async (req:Request, res:Response): Promise<Response> => {
return res.send({project, tasks}) return res.send({project, tasks})
}) })
/** Delete project by project ID*/
router.delete('/:projectId',async (req: Request, res: Response):Promise<Response>=>{ router.delete('/:projectId',async (req: Request, res: Response):Promise<Response>=>{
const projectId = req.params.projectId; const projectId = req.params.projectId;
await myDataSource await myDataSource
...@@ -123,6 +103,7 @@ router.delete('/:projectId',async (req: Request, res: Response):Promise<Response ...@@ -123,6 +103,7 @@ router.delete('/:projectId',async (req: Request, res: Response):Promise<Response
}) })
/** Get projects were user is admin, by user ID*/
router.get('/user/:userId', async (req : Request, res : Response): Promise<Response>=>{ router.get('/user/:userId', async (req : Request, res : Response): Promise<Response>=>{
const userId:string = req.params.userId const userId:string = req.params.userId
...@@ -157,16 +138,13 @@ router.post('/add-user/', async (req: Request, res: Response):Promise<Response>= ...@@ -157,16 +138,13 @@ router.post('/add-user/', async (req: Request, res: Response):Promise<Response>=
/** Remove user from specific project */ /** Remove user from specific project */
router.post('/remove-user', async (req: Request, res: Response):Promise<Response>=> { router.post('/remove-user', async (req: Request, res: Response):Promise<Response>=> {
console.log('in delete user')
const token = req.get('Authorization'); const token = req.get('Authorization');
console.log('token ', token)
const {userId, projectId} = req.body; const {userId, projectId} = req.body;
console.log('req.body', req.body )
const adminOfProject = await dataSource const adminOfProject = await dataSource
.createQueryBuilder() .createQueryBuilder()
.select("user") .select("user")
.from(User, "user") .from(User, "user")
.leftJoinAndSelect("user.membership","member") .leftJoinAndSelect("user.members","member")
.leftJoinAndSelect('member.project', 'project' ) .leftJoinAndSelect('member.project', 'project' )
.where("user.token = :token", { token }) .where("user.token = :token", { token })
.andWhere('project.id=:projectId',{projectId}) .andWhere('project.id=:projectId',{projectId})
...@@ -175,9 +153,7 @@ router.post('/remove-user', async (req: Request, res: Response):Promise<Response ...@@ -175,9 +153,7 @@ router.post('/remove-user', async (req: Request, res: Response):Promise<Response
if (!adminOfProject){ if (!adminOfProject){
return res.send({message:'User is not authorized'}) return res.send({message:'User is not authorized'})
} }
// return res.send({adminOfProject})
console.log('adminOfProject', adminOfProject)
try{ try{
await dataSource await dataSource
.createQueryBuilder() .createQueryBuilder()
......
import express,{Router, Request, Response} from 'express'; import express,{Router, Request, Response } from 'express';
import {Task} from '../models/Task'; import {Task} from '../models/Task';
import {myDataSource} from '../app-data-source'; import {myDataSource} from '../app-data-source';
import { User } from '../models/User'; import { User } from '../models/User';
import { Member } from '../models/Member'; import { Member } from '../models/Member';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { DateTimeTask } from '../models/DateTimeTask';
import { auth, authAuthorOrExecutorOfTask, taskFinderById } from '../helpers';
const router:Router = express.Router(); const router:Router = express.Router();
const dataSource = myDataSource; const dataSource = myDataSource;
/**get all tasks */
router.get('/', async(req:Request, res:Response):Promise<Response> => { router.get('/', async(req:Request, res:Response):Promise<Response> => {
const tasks = await dataSource const tasks = await dataSource
.getRepository(Task) .getRepository(Task)
...@@ -19,29 +22,24 @@ router.get('/', async(req:Request, res:Response):Promise<Response> => { ...@@ -19,29 +22,24 @@ router.get('/', async(req:Request, res:Response):Promise<Response> => {
return res.send({tasks}) return res.send({tasks})
}) })
router.post('/', async(req:Request, res:Response):Promise<Response>=>{
const token = req.get('Authorization'); /**create new task */
router.post('/', auth, async(req:Request, res:Response):Promise<Response>=>{
const {user,title,description,project,executor, dateTimeDeadLine,priority} = req.body;
const newTask = new Task(); const newTask = new Task();
const {title,description,project,dateTimeDue,dateTimeStart,accomplish,priority} = req.body;
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.token = :token", { token: token })
.getOne()
if (!user) return res.status(404).send({Message:'user not found'})
newTask.title = title; newTask.title = title;
newTask.description = description; newTask.description = description;
newTask.project = project; newTask.project = project;
newTask.dateTimeDue = dateTimeDue; newTask.dateTimeDeadLine=dateTimeDeadLine;
newTask.dateTimeStart = dateTimeStart;
newTask.author= user; newTask.author= user;
newTask.accomplish = accomplish; newTask.executor= executor;
newTask.priority = priority; newTask.priority = priority;
await newTask.save(); await newTask.save();
return res.send({newTask}) return res.send({newTask});
}) })
/**check tasks of specific user by userID */
router.get('/user/:userId', async (req: Request, res: Response):Promise<Response>=>{ router.get('/user/:userId', async (req: Request, res: Response):Promise<Response>=>{
const userId = req.params.userId; const userId = req.params.userId;
const tasks = await dataSource const tasks = await dataSource
...@@ -67,15 +65,10 @@ router.get('/user/:userId', async (req: Request, res: Response):Promise<Response ...@@ -67,15 +65,10 @@ router.get('/user/:userId', async (req: Request, res: Response):Promise<Response
return res.send({tasks}) return res.send({tasks})
}) })
router.get('/my', async (req: Request, res: Response):Promise<Response>=>{
const token = req.get('Authorization'); /**check tasks of current user where he is author or executor, search by id*/
const user = await dataSource router.get('/my',auth, async (req: Request, res: Response):Promise<Response>=>{
.createQueryBuilder() const user = req.body.user
.select("user")
.from(User, "user")
.where("user.token = :token", { token: token })
.getOne()
if(!user) return res.status(404).send({Message:'user not found'})
const tasks = await dataSource const tasks = await dataSource
.getRepository(Task) .getRepository(Task)
.find({ .find({
...@@ -100,60 +93,51 @@ router.get('/my', async (req: Request, res: Response):Promise<Response>=>{ ...@@ -100,60 +93,51 @@ router.get('/my', async (req: Request, res: Response):Promise<Response>=>{
}) })
router.get('/related', async (req: Request, res: Response):Promise<Response>=>{ /**return tasks & users of projects in which current user is involved, search by token*/
const token = req.get('Authorization'); router.get('/related', auth,async (req: Request, res: Response):Promise<Response>=>{
const user = await dataSource console.log('related')
.createQueryBuilder() const user = req.body.user
.select("user") const tasks = await dataSource
.from(User, "user")
.where("user.token = :token", { token: token })
.getOne()
if(!user) return res.status(404).send({Message:'user not found'})
const rawMembership = await dataSource
.createQueryBuilder() .createQueryBuilder()
.select("member") .select(["task"])
.from(Member, "member") .from(Task,"task")
.leftJoinAndSelect('member.user', 'user' ) .leftJoinAndSelect("task.executor","user")
.leftJoinAndSelect('member.project', 'project' ) .leftJoinAndSelect("task.dateTimeTasks","dateTimeTask")
.leftJoinAndSelect('project.tasks', 'task' ) .leftJoinAndSelect("task.author","users")
.where("member.userId = :id", { id: user.id }) .leftJoinAndSelect("task.project","project")
.leftJoinAndSelect("project.members", "member")
.where("member.userId = :userId", {userId:user.id})
.getMany() .getMany()
let projectIds:any[] = [] let projectIds:any[] = []
if (rawMembership.length>0){ if (tasks.length>0){
for (let member of rawMembership){ for (let task of tasks){
const projectId = member?.project?.id const projectId = task?.project?.id
projectIds.push(projectId) projectIds.push(projectId)
} }
} }
let tasks :Task[]= []
const projectsIdArray = [...new Set(projectIds)]; const projectsIdArray = [...new Set(projectIds)];
const searchByProjectsIdArray:object[] = [] let users:User[]=[]
for (let projectId of projectsIdArray){
searchByProjectsIdArray.push({id:projectId})
}
if (projectIds.length>0){ if (projectIds.length>0){
tasks = await dataSource users = await dataSource
.getRepository(Task) .getRepository(User)
.find({ .find({
relations:{ relations:{
author:true, members:true,
executor:true,
project:true
}, },
where:{ where:{
project: members:
{id:In( {project:In(
projectsIdArray projectsIdArray
)} )}
} }
}) })
} }
return res.send({tasks}) return res.send({tasks, users})
}) })
/**delete of task by task id */
router.delete('/:taskId',async (req: Request, res: Response):Promise<Response>=>{ router.delete('/:taskId',async (req: Request, res: Response):Promise<Response>=>{
const taskId = req.params.taskId; const taskId = req.params.taskId;
await myDataSource await myDataSource
...@@ -166,39 +150,48 @@ router.delete('/:taskId',async (req: Request, res: Response):Promise<Response>=> ...@@ -166,39 +150,48 @@ router.delete('/:taskId',async (req: Request, res: Response):Promise<Response>=>
}) })
router.put('/',async(req:Request, res:Response)=> {
const token = req.get('Authorization');
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.token = :token", { token: token })
.getOne()
if (!user) return res.status(404).send({Message:'user not found'})
const {id,title,description,project,dateTimeDue,dateTimeStart,executors,accomplish,priority} = req.body;
const task = await dataSource
.createQueryBuilder()
.select("task")
.from(Task, "task")
.where("task.id = :id", { id })
.getOne()
/**change of task by task id */
router.put('/',authAuthorOrExecutorOfTask,async(req:Request, res:Response)=> {
const {authorStatus,executorStatus,taskId,title,description,note, archive,project,dateTimeTaskId,start,due,executor,accomplish,dateTimeDeadLine, dateTimeFactDeadLine,priority} = req.body;
const task = await taskFinderById(taskId)
if (!task) return res.status(404).send({Message:'task not found'}) if (!task) return res.status(404).send({Message:'task not found'})
task.title= title let dateTimeTask = null;
task.description= description if (dateTimeTaskId) {
task.project= project const dateTimeTaskData = await dataSource
task.dateTimeDue= dateTimeDue .createQueryBuilder()
task.dateTimeStart= dateTimeStart .select("dateTimeTask")
task.author=user .from(DateTimeTask, "dateTimeTask")
// task.executors=executors .where("dateTimeTask,id=:dateTimeTaskId",{dateTimeTaskId})
.getOne()
if (!dateTimeTask) return res.status(404).send({Message:'dateTimeTask not found'})
dateTimeTask = dateTimeTaskData
}
if (authorStatus){
task.title = title
task.description= description
task.archive=archive
task.dateTimeDeadLine=dateTimeDeadLine;
task.project=project
task.executor=executor
task.priority= priority
}
if(executorStatus && dateTimeTask!==null){
dateTimeTask.dateTimeStart = start
dateTimeTask.dateTimeDue = due
await dateTimeTask.save()
task.note = note
task.dateTimeFactDeadLine= dateTimeFactDeadLine
}
task.accomplish= accomplish task.accomplish= accomplish
task.priority= priority
await task.save() await task.save()
res.send({message:'update task successfully'}) res.send({message:'update task successfully'})
}) })
/** search all tasks on given projects: project Id array[] */
router.post('/project',async (req: Request, res: Response):Promise<Response>=>{ router.post('/project',async (req: Request, res: Response):Promise<Response>=>{
let projectArray :string[]= req.body; let projectArray :string[]= req.body;
if (projectArray.length===0) { if (projectArray.length===0) {
...@@ -207,7 +200,7 @@ router.post('/project',async (req: Request, res: Response):Promise<Response>=>{ ...@@ -207,7 +200,7 @@ router.post('/project',async (req: Request, res: Response):Promise<Response>=>{
.getRepository(Task) .getRepository(Task)
.find({ .find({
relations:{ relations:{
// executors:true, executor:true,
author:true, author:true,
project:true project:true
} }
...@@ -227,4 +220,6 @@ router.post('/project',async (req: Request, res: Response):Promise<Response>=>{ ...@@ -227,4 +220,6 @@ router.post('/project',async (req: Request, res: Response):Promise<Response>=>{
export default router; export default router;
...@@ -5,6 +5,8 @@ import { nanoid } from 'nanoid'; ...@@ -5,6 +5,8 @@ import { nanoid } from 'nanoid';
import multer from 'multer'; import multer from 'multer';
import path from 'path'; import path from 'path';
import {config} from "../config" import {config} from "../config"
import { Project } from '../models/Project';
import { Member, MemberRole } from '../models/Member';
const router:Router = express.Router(); const router:Router = express.Router();
const dataSource = myDataSource; const dataSource = myDataSource;
...@@ -24,16 +26,16 @@ const storage = multer.diskStorage({ ...@@ -24,16 +26,16 @@ const storage = multer.diskStorage({
const upload = multer({ storage }) const upload = multer({ storage })
//** return all users of DB */
router.get('/', async (req : Request, res : Response):Promise<object> => { router.get('/', async (req : Request, res : Response):Promise<object> => {
const users = await dataSource const users = await dataSource
.getRepository(User) .getRepository(User)
.createQueryBuilder("user") .createQueryBuilder("user")
.getMany() .getMany()
return res.send({users}) return res.send({users})
}) })
/**create new user*/
router.post('/', upload.single("avatar"), async (req : Request, res : Response):Promise<object> => { router.post('/', upload.single("avatar"), async (req : Request, res : Response):Promise<object> => {
const {name,surname,password,email, role} = req.body; const {name,surname,password,email, role} = req.body;
const displayName = surname+' '+name[0]+'.' const displayName = surname+' '+name[0]+'.'
...@@ -44,15 +46,25 @@ router.post('/', upload.single("avatar"), async (req : Request, res : Response): ...@@ -44,15 +46,25 @@ router.post('/', upload.single("avatar"), async (req : Request, res : Response):
user.displayName= displayName; user.displayName= displayName;
user.email = email; user.email = email;
user.role = role; user.role = role;
user.generateToken() user.generateToken();
await user.save(); await user.save();
const newProject = new Project();
newProject.title = 'Личные дела';
newProject.color= 'Green';
await newProject.save()
const newMember = new Member();
newMember.user = user;
newMember.project = newProject;
newMember.roleProject = MemberRole.ADMIN;
await newMember.save();
const userToFront:User|null = await dataSource.manager.findOneBy(User, { const userToFront:User|null = await dataSource.manager.findOneBy(User, {
email: user.email email: user.email
}) })
return res.send({userToFront}) return res.send({userToFront})
}) })
/** log in*/
router.post('/sessions/', async (req : Request, res : Response):Promise<object> => { router.post('/sessions/', async (req : Request, res : Response):Promise<object> => {
const {email, password} = req.body; const {email, password} = req.body;
const user = await dataSource const user = await dataSource
...@@ -77,7 +89,7 @@ router.post('/sessions/', async (req : Request, res : Response):Promise<object> ...@@ -77,7 +89,7 @@ router.post('/sessions/', async (req : Request, res : Response):Promise<object>
}) })
}) })
/** log out*/
router.delete('/sessions', async(req: Request, res: Response):Promise<void | object> => { router.delete('/sessions', async(req: Request, res: Response):Promise<void | object> => {
const token = req.get('Authorization'); const token = req.get('Authorization');
const successMsg = {message:'success'}; const successMsg = {message:'success'};
......
...@@ -4,6 +4,7 @@ import users from './routers/users'; ...@@ -4,6 +4,7 @@ import users from './routers/users';
import tasks from './routers/tasks'; import tasks from './routers/tasks';
import projects from './routers/projects'; import projects from './routers/projects';
import {myDataSource} from './app-data-source'; import {myDataSource} from './app-data-source';
import copyTasks from './routers/copyTasks';
myDataSource myDataSource
...@@ -22,6 +23,7 @@ app.use(express.json()); ...@@ -22,6 +23,7 @@ app.use(express.json());
const PORT = 8000; const PORT = 8000;
app.use('/users',users) app.use('/users',users)
app.use('/tasks',tasks) app.use('/tasks',tasks)
app.use('/copy-tasks',copyTasks)
app.use('/projects',projects) app.use('/projects',projects)
const run = async() => { const run = async() => {
......
import * as React from "react"; import * as React from "react";
import TableCell from "@mui/material/TableCell"; import TableCell from "@mui/material/TableCell";
import Input from "@mui/material/Input"; import Input from "@mui/material/Input";
import moment from "moment";
const CustomTableCell = ({task, const CustomTableCell = ({task,
name, name,
value, value,
value2,
value3,
onChange, onChange,
onModalOpen, onModalOpen,
placeholder, placeholder,
user user
}) => { }) => {
const styles = { width: "auto", height: "10px"}; const styles = { width: "auto", height: "10px"};
const divStyle={display:"flex",justifyContent:"space-between", flexDirection:"column",fontSize:"12px"}
const duration = moment.duration(moment(task.dateTimeTasks[0]?.dateTimeDue).diff(moment(task.dateTimeTasks[0]?.dateTimeStart)));
const hours = Math.round(duration.asHours());
if (task) { if (task) {
return ( return (
...@@ -21,7 +26,7 @@ const CustomTableCell = ({task, ...@@ -21,7 +26,7 @@ const CustomTableCell = ({task,
align="left" align="left"
style={styles} style={styles}
> >
{task.isEditMode && onChange && name==!"author" && task.author.id==user.id ? ( {task.isEditMode && onChange && name!=="author" && task.author.id===user.id ? (
<Input <Input
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}
...@@ -29,9 +34,16 @@ const CustomTableCell = ({task, ...@@ -29,9 +34,16 @@ const CustomTableCell = ({task,
onChange={(e) => onChange(e, task)} onChange={(e) => onChange(e, task)}
style={styles} style={styles}
/> />
) : ( ) : name!=="dateTimeStart" ? (
<span>{value}</span> <span>{value}</span>
)} ):(
<div style={divStyle}>
<span>{value}</span>
<span>{value2}</span>
<span>часы:+{hours}</span>
</div>
)
}
</TableCell> </TableCell>
</> </>
); );
......
...@@ -25,7 +25,7 @@ import MaterialUIPickers from "../../components/MyTasksCompoments/DateTimePicker ...@@ -25,7 +25,7 @@ import MaterialUIPickers from "../../components/MyTasksCompoments/DateTimePicker
import BasicSelect from "../../components/UI/Select/Select"; import BasicSelect from "../../components/UI/Select/Select";
import { fetchAllTasks, deleteTask,editTask} from "../../store/actions/tasksActions"; import { fetchAllTasks, deleteTask,editTask} from "../../store/actions/tasksActions";
import NewTaskForm from "../../components/MyTasksCompoments/NewTaskForm"; import NewTaskForm from "../../components/MyTasksCompoments/NewTaskForm";
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
export default function EnhancedTable() { export default function EnhancedTable() {
...@@ -45,7 +45,9 @@ export default function EnhancedTable() { ...@@ -45,7 +45,9 @@ export default function EnhancedTable() {
task: null, task: null,
}); });
const [projects,setProjects]=useState(['1','2']) const [projects,setProjects]=useState(['1','2'])
useEffect(() => { useEffect(() => {
dispatch(fetchAllTasks()); dispatch(fetchAllTasks());
filterProjectsNamesFromTasks() filterProjectsNamesFromTasks()
...@@ -207,7 +209,6 @@ useEffect(() => { ...@@ -207,7 +209,6 @@ useEffect(() => {
} }
let uniqueTitlesProjects = [...new Set(rawSetProjectNames)]; let uniqueTitlesProjects = [...new Set(rawSetProjectNames)];
setProjects(uniqueTitlesProjects) setProjects(uniqueTitlesProjects)
} }
} }
...@@ -221,7 +222,7 @@ useEffect(() => { ...@@ -221,7 +222,7 @@ useEffect(() => {
return results; return results;
}, []); }, []);
console.log(tasks)
if ( if (
tasks && tasks &&
tasks?.length > 0 && tasks?.length > 0 &&
...@@ -353,26 +354,79 @@ useEffect(() => { ...@@ -353,26 +354,79 @@ useEffect(() => {
user:user user:user
}} }}
/> />
{task.isEditMode && task.author.id===user.id ? (
<TableCell> <TableCell>
<MaterialUIPickers {/* <MaterialUIPickers
task={task} task={task}
name="dateTimeStart" name="dateTimeStart"
onChange={onDateChange} onChange={onDateChange}
user={user} user={user}
/> /> */}
<TableCell>
<Tooltip title="Перейти в календарь">
<IconButton
onClick={(id) => { deleteHandle(task.id);}}
>
<CalendarMonthIcon />
</IconButton>
</Tooltip>
</TableCell> </TableCell>
</TableCell>
) : (<>
<CustomTableCell
{...{
task,
name: "dateTimeStart",
value: moment(task.dateTimeTasks[0]?.dateTimeStart)
.utc()
.format("DD-MM-YYYY "),
value2:moment(task.dateTimeTasks[0]?.dateTimeStart)
.utc()
.format("hh:mm A")+"-"+
moment(task.dateTimeTasks[0]?.dateTimeDue)
.utc()
.format("hh:mm A"),
user:user
}}>
{/* <span>"время завершения"+ {moment(task.dateTimeDue)
.utc()
.format("hh:mm A")}</span> */}
</CustomTableCell>
</>
)}
<TableCell> {/* <TableCell>
<MaterialUIPickers <MaterialUIPickers
task={task}
name="dateTimeStart"
onChange={onDateChange}
user={user}
/>
</TableCell> */}
{task.isEditMode && task.author.id===user.id ? (
<TableCell>
<MaterialUIPickers
task={task} task={task}
name="dateTimeDue" name="dateTimeDue"
onChange={onDateChange} onChange={onDateChange}
user={user} user={user}
/> />
</TableCell> </TableCell>
) : (
<CustomTableCell
{...{
task,
name: "dateTimeDeadLine",
value: moment(task.dateTimeDeadLine
)
.utc()
.format("DD-MM-YYYY hh:mm A"),
user:user
}}
/>
)}
{task.isEditMode ? ( {task.isEditMode ? (
<TableCell> <TableCell>
<BasicSelect <BasicSelect
......
...@@ -49,13 +49,13 @@ const headCells = [ ...@@ -49,13 +49,13 @@ const headCells = [
id: "dateTimeStart", id: "dateTimeStart",
numeric: true, numeric: true,
disablePadding: false, disablePadding: false,
label: "Дата начала", label: "Дата и время выполнения",
}, },
{ {
id: "dateTimeDue", id: "dateTimeDue",
numeric: true, numeric: true,
disablePadding: false, disablePadding: false,
label: ата завершения", label: едлайн",
}, },
{ {
id: "accomplish", id: "accomplish",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment