Implemented project settings page, search issues modal, general refactoring
This commit is contained in:
5
api/package-lock.json
generated
5
api/package-lock.json
generated
@@ -3870,6 +3870,11 @@
|
||||
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
|
||||
"dev": true
|
||||
},
|
||||
"striptags": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz",
|
||||
"integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"module-alias": "^2.2.2",
|
||||
"pg": "^7.14.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"striptags": "^3.1.1",
|
||||
"typeorm": "^0.2.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -6,6 +6,27 @@ import { updateEntity, deleteEntity, createEntity, findEntityOrThrow } from 'uti
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/issues',
|
||||
catchErrors(async (req, res) => {
|
||||
const { projectId } = req.currentUser;
|
||||
const { searchTerm } = req.query;
|
||||
|
||||
let whereSQL = 'issue.projectId = :projectId';
|
||||
|
||||
if (searchTerm) {
|
||||
whereSQL += ' AND (issue.title ILIKE :searchTerm OR issue.descriptionText ILIKE :searchTerm)';
|
||||
}
|
||||
|
||||
const issues = await Issue.createQueryBuilder('issue')
|
||||
.select()
|
||||
.where(whereSQL, { projectId, searchTerm: `%${searchTerm}%` })
|
||||
.getMany();
|
||||
|
||||
res.respond({ issues });
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/issues/:issueId',
|
||||
catchErrors(async (req, res) => {
|
||||
@@ -41,11 +62,11 @@ router.delete(
|
||||
}),
|
||||
);
|
||||
|
||||
const calculateListPosition = async (newIssue: Issue): Promise<number> => {
|
||||
const issues = await Issue.find({
|
||||
where: { projectId: newIssue.projectId, status: newIssue.status },
|
||||
});
|
||||
const calculateListPosition = async ({ projectId, status }: Issue): Promise<number> => {
|
||||
const issues = await Issue.find({ projectId, status });
|
||||
|
||||
const listPositions = issues.map(({ listPosition }) => listPosition);
|
||||
|
||||
if (listPositions.length > 0) {
|
||||
return Math.min(...listPositions) - 1;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ router.get(
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/projects/:projectId',
|
||||
'/project',
|
||||
catchErrors(async (req, res) => {
|
||||
const project = await updateEntity(Project, req.params.projectId, req.body);
|
||||
const project = await updateEntity(Project, req.currentUser.projectId, req.body);
|
||||
res.respond({ project });
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ const createDatabaseConnection = (): Promise<Connection> =>
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
entities: Object.values(entities),
|
||||
synchronize: true,
|
||||
});
|
||||
|
||||
export default createDatabaseConnection;
|
||||
|
||||
@@ -74,8 +74,7 @@ const seedIssues = (project: Project): Promise<Issue[]> => {
|
||||
status: IssueStatus.BACKLOG,
|
||||
priority: IssuePriority.HIGH,
|
||||
listPosition: 4,
|
||||
description:
|
||||
"#### Colons can be used to align columns.\n\n| Tables | Are | Cool |\n| ------------- |:-------------:| -----:|\n| col 3 is | right-aligned | |\n| col 2 is | centered | |\n| zebra stripes | are neat | |\n\nThe outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.\n\nMarkdown | Less | Pretty\n--- | --- | ---\n*Still* | `renders` | **nicely**\n1 | 2 | 3",
|
||||
description: '#### Colons can be used to align columns.',
|
||||
estimate: 4,
|
||||
reporterId: getRandomUser().id,
|
||||
project,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import striptags from 'striptags';
|
||||
import {
|
||||
BaseEntity,
|
||||
Entity,
|
||||
@@ -10,6 +11,8 @@ import {
|
||||
ManyToMany,
|
||||
JoinTable,
|
||||
RelationId,
|
||||
BeforeUpdate,
|
||||
BeforeInsert,
|
||||
} from 'typeorm';
|
||||
|
||||
import is from 'utils/validation';
|
||||
@@ -48,6 +51,9 @@ class Issue extends BaseEntity {
|
||||
@Column('text', { nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column('text', { nullable: true })
|
||||
descriptionText: string | null;
|
||||
|
||||
@Column('integer', { nullable: true })
|
||||
estimate: number | null;
|
||||
|
||||
@@ -90,6 +96,14 @@ class Issue extends BaseEntity {
|
||||
|
||||
@RelationId((issue: Issue) => issue.users)
|
||||
userIds: number[];
|
||||
|
||||
@BeforeInsert()
|
||||
@BeforeUpdate()
|
||||
setDescriptionText = (): void => {
|
||||
if (this.description) {
|
||||
this.descriptionText = striptags(this.description);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default Issue;
|
||||
|
||||
@@ -17,7 +17,6 @@ class Project extends BaseEntity {
|
||||
static validations = {
|
||||
name: [is.required(), is.maxLength(100)],
|
||||
url: is.url(),
|
||||
description: is.maxLength(10000),
|
||||
category: [is.required(), is.oneOf(Object.values(ProjectCategory))],
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export const errorHandler: ErrorRequestHandler = (error, _req, res, _next) => {
|
||||
|
||||
const isErrorSafeForClient = error instanceof CustomError;
|
||||
|
||||
const errorData = isErrorSafeForClient
|
||||
const clientError = isErrorSafeForClient
|
||||
? pick(error, ['message', 'code', 'status', 'data'])
|
||||
: {
|
||||
message: 'Something went wrong, please contact our support.',
|
||||
@@ -17,5 +17,5 @@ export const errorHandler: ErrorRequestHandler = (error, _req, res, _next) => {
|
||||
data: {},
|
||||
};
|
||||
|
||||
res.status(errorData.status).send({ error: errorData });
|
||||
res.status(clientError.status).send({ error: clientError });
|
||||
};
|
||||
|
||||
@@ -4,12 +4,6 @@ import { verifyToken } from 'utils/authToken';
|
||||
import { catchErrors, InvalidTokenError } from 'errors';
|
||||
import { User } from 'entities';
|
||||
|
||||
const getAuthTokenFromRequest = (req: Request): string | null => {
|
||||
const header = req.get('Authorization') || '';
|
||||
const [bearer, token] = header.split(' ');
|
||||
return bearer === 'Bearer' && token ? token : null;
|
||||
};
|
||||
|
||||
export const authenticateUser = catchErrors(async (req, _res, next) => {
|
||||
const token = getAuthTokenFromRequest(req);
|
||||
if (!token) {
|
||||
@@ -26,3 +20,9 @@ export const authenticateUser = catchErrors(async (req, _res, next) => {
|
||||
req.currentUser = user;
|
||||
next();
|
||||
});
|
||||
|
||||
const getAuthTokenFromRequest = (req: Request): string | null => {
|
||||
const header = req.get('Authorization') || '';
|
||||
const [bearer, token] = header.split(' ');
|
||||
return bearer === 'Bearer' && token ? token : null;
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@ export const generateErrors = (
|
||||
Object.entries(fieldValidators).forEach(([fieldName, validators]) => {
|
||||
[validators].flat().forEach(validator => {
|
||||
const errorMessage = validator(fieldValues[fieldName], fieldValues);
|
||||
|
||||
if (errorMessage !== false && !fieldErrors[fieldName]) {
|
||||
fieldErrors[fieldName] = errorMessage;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user