Skip to content

Commit 53cbd23

Browse files
author
Parth Shah
committed
Merge branch 'dev'
2 parents 281eb39 + 007c751 commit 53cbd23

File tree

8 files changed

+643
-554
lines changed

8 files changed

+643
-554
lines changed

circle.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ deployment:
2424

2525

2626
production:
27-
# branch: master
28-
# commands:
29-
# - ./ebs_deploy.sh tc-project-service PROD $CIRCLE_BUILD_NUM
30-
31-
tag: /v[0-9]+(\.[0-9]+)*/
32-
owner: appirio-tech
27+
branch: master
3328
commands:
34-
- ./ebs_deploy.sh tc-project-service PROD $CIRCLE_TAG
29+
- ./ebs_deploy.sh tc-project-service PROD $CIRCLE_BUILD_NUM
30+
31+
# tag: /v[0-9]+(\.[0-9]+)*/
32+
# owner: appirio-tech
33+
# commands:
34+
# - ./ebs_deploy.sh tc-project-service PROD $CIRCLE_TAG
3535

3636
general:
3737
artifacts:
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ALTER TABLE projects ADD COLUMN "projectFullText" text;
2+
UPDATE projects SET "projectFullText" = lower(name || ' ' || coalesce(description, '') || ' ' || coalesce(details#>>'{utm, code}', ''));
3+
CREATE EXTENSION IF NOT EXISTS pg_trgm;
4+
CREATE INDEX project_text_search_idx ON projects USING GIN("projectFullText" gin_trgm_ops);
5+
6+
CREATE OR REPLACE FUNCTION project_text_update_trigger() RETURNS trigger AS $$
7+
begin
8+
new."projectFullText" :=
9+
lower(new.name || ' ' || coalesce(new.description, '') || ' ' || coalesce(new.details#>>'{utm, code}', ''));
10+
return new;
11+
end
12+
$$ LANGUAGE plpgsql;
13+
14+
DROP TRIGGER IF EXISTS project_text_update ON projects;
15+
CREATE TRIGGER project_text_update BEFORE INSERT OR UPDATE ON projects FOR EACH ROW EXECUTE PROCEDURE project_text_update_trigger();

src/index.js

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,44 @@
1-
'use strict'
2-
3-
// include newrelic
4-
if (process.env.NODE_ENV !== 'test' && process.env.NODE_ENV !== 'local') {
5-
require('newrelic')
6-
}
7-
8-
const app = require('./app')
9-
10-
11-
/**
12-
* Handle server shutdown gracefully
13-
*/
14-
function gracefulShutdown() {
15-
app.services.pubsub.disconnect()
16-
.then(()=> {
17-
app.logger.info('Gracefully shutting down server')
18-
process.exit()
19-
}).catch((err) => {
20-
app.logger.error(err)
21-
})
22-
// if after
23-
setTimeout(function() {
24-
app.logger.error("Could not close connections in time, forcefully shutting down");
25-
process.exit()
26-
}, 10*1000);
27-
}
28-
process.on('SIGTERM', gracefulShutdown)
29-
process.on('SIGINT', gracefulShutdown)
30-
31-
// =======================
32-
// start the server ======
33-
// =======================
34-
var port = process.env.PORT || 3000 // used to create, sign, and verify tokens
35-
36-
var server = app.listen(port, () => {
37-
app.logger.info("Starting server on PORT: %d", port)
38-
let authz = require('tc-core-library-js').Authorizer
39-
app.logger.info("Registered Policies", authz.getRegisteredPolicies())
40-
require('express-list-routes')({prefix: '', spacer: 7}, 'APIs:', app.routerRef)
41-
})
42-
43-
module.exports = server
1+
'use strict'
2+
3+
import models from './models'
4+
5+
// include newrelic
6+
if (process.env.NODE_ENV !== 'test' && process.env.NODE_ENV !== 'local') {
7+
require('newrelic')
8+
}
9+
10+
const app = require('./app')
11+
12+
/**
13+
* Handle server shutdown gracefully
14+
*/
15+
function gracefulShutdown() {
16+
app.services.pubsub.disconnect()
17+
.then(()=> {
18+
app.logger.info('Gracefully shutting down server')
19+
process.exit()
20+
}).catch((err) => {
21+
app.logger.error(err)
22+
})
23+
// if after
24+
setTimeout(function() {
25+
app.logger.error("Could not close connections in time, forcefully shutting down");
26+
process.exit()
27+
}, 10*1000);
28+
}
29+
process.on('SIGTERM', gracefulShutdown)
30+
process.on('SIGINT', gracefulShutdown)
31+
32+
// =======================
33+
// start the server ======
34+
// =======================
35+
var port = process.env.PORT || 3000 // used to create, sign, and verify tokens
36+
37+
var server = app.listen(port, () => {
38+
app.logger.info("Starting server on PORT: %d", port)
39+
let authz = require('tc-core-library-js').Authorizer
40+
app.logger.info("Registered Policies", authz.getRegisteredPolicies())
41+
require('express-list-routes')({prefix: '', spacer: 7}, 'APIs:', app.routerRef)
42+
})
43+
44+
module.exports = server

src/models/project.js

Lines changed: 156 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,156 @@
1-
'use strict'
2-
import { PROJECT_TYPE, PROJECT_STATUS, PROJECT_MEMBER_ROLE } from '../constants'
3-
import _ from 'lodash'
4-
5-
module.exports = function(sequelize, DataTypes) {
6-
var Project = sequelize.define('Project', {
7-
id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
8-
directProjectId: DataTypes.BIGINT,
9-
billingAccountId: DataTypes.BIGINT,
10-
name: { type: DataTypes.STRING, allowNull: false },
11-
description: DataTypes.TEXT,
12-
external: DataTypes.JSON,
13-
bookmarks: DataTypes.JSON,
14-
utm: { type: DataTypes.JSON, allowNull: true },
15-
estimatedPrice: { type: DataTypes.DECIMAL(10,2), allowNull: true },
16-
actualPrice: { type: DataTypes.DECIMAL(10,2), allowNull: true},
17-
terms: {
18-
type: DataTypes.ARRAY(DataTypes.INTEGER),
19-
allowNull: false,
20-
defaultValue: []
21-
},
22-
type: {
23-
type: DataTypes.STRING,
24-
allowNull: false,
25-
validate: {
26-
isIn: [_.values(PROJECT_TYPE)]
27-
}
28-
},
29-
status: {
30-
type: DataTypes.STRING,
31-
allowNull: false,
32-
validate: {
33-
isIn: [_.values(PROJECT_STATUS)]
34-
}
35-
},
36-
details: { type: DataTypes.JSON },
37-
challengeEligibility: DataTypes.JSON,
38-
deletedAt: { type: DataTypes.DATE, allowNull: true },
39-
createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
40-
updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
41-
createdBy: { type: DataTypes.INTEGER, allowNull: false },
42-
updatedBy: { type: DataTypes.INTEGER, allowNull: false }
43-
}, {
44-
tableName: 'projects',
45-
timestamps: true,
46-
updatedAt: 'updatedAt',
47-
createdAt: 'createdAt',
48-
deletedAt: 'deletedAt',
49-
indexes: [
50-
{ fields: ['createdAt'] },
51-
{ fields: ['name'] },
52-
{ fields: ['type'] },
53-
{ fields: ['status'] },
54-
{ fields: ['directProjectId'] }
55-
],
56-
classMethods: {
57-
/*
58-
* @Co-pilots should be able to view projects any of the following conditions are met:
59-
* a. they are registered active project members on the project
60-
* b. any project that is in 'reviewed' state AND does not yet have a co-pilot assigned
61-
* @param userId the id of user
62-
*/
63-
getProjectIdsForCopilot: function(userId) {
64-
return this.findAll({
65-
where: {
66-
$or: [
67-
['EXISTS(SELECT * FROM "project_members" WHERE "deletedAt" IS NULL AND "projectId" = "Project".id AND "userId" = ? )', userId],
68-
['"Project".status=? AND NOT EXISTS(SELECT * FROM "project_members" WHERE "deletedAt" IS NULL AND "projectId" = "Project".id AND "role" = ? )',
69-
PROJECT_STATUS.REVIEWED, PROJECT_MEMBER_ROLE.COPILOT]
70-
]
71-
},
72-
attributes:['id'],
73-
raw: true
74-
})
75-
.then((res) => {
76-
return _.map(res, 'id')
77-
})
78-
},
79-
/**
80-
* Get direct project id
81-
* @param id the id of project
82-
*/
83-
getDirectProjectId: function(id) {
84-
return this.findById(id, {
85-
attributes:['directProjectId'],
86-
raw: true
87-
})
88-
.then((res) => {
89-
return res.directProjectId
90-
})
91-
},
92-
associate: (models) => {
93-
Project.hasMany(models.ProjectMember, { as : 'members', foreignKey: 'projectId' })
94-
Project.hasMany(models.ProjectAttachment, { as : 'attachments', foreignKey: 'projectId' })
95-
}
96-
}
97-
})
98-
99-
return Project
100-
}
1+
'use strict'
2+
import { PROJECT_TYPE, PROJECT_STATUS, PROJECT_MEMBER_ROLE } from '../constants'
3+
import _ from 'lodash'
4+
5+
module.exports = function(sequelize, DataTypes) {
6+
var Project = sequelize.define('Project', {
7+
id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
8+
directProjectId: DataTypes.BIGINT,
9+
billingAccountId: DataTypes.BIGINT,
10+
name: { type: DataTypes.STRING, allowNull: false },
11+
description: DataTypes.TEXT,
12+
external: DataTypes.JSON,
13+
bookmarks: DataTypes.JSON,
14+
utm: { type: DataTypes.JSON, allowNull: true },
15+
estimatedPrice: { type: DataTypes.DECIMAL(10,2), allowNull: true },
16+
actualPrice: { type: DataTypes.DECIMAL(10,2), allowNull: true},
17+
terms: {
18+
type: DataTypes.ARRAY(DataTypes.INTEGER),
19+
allowNull: false,
20+
defaultValue: []
21+
},
22+
type: {
23+
type: DataTypes.STRING,
24+
allowNull: false,
25+
validate: {
26+
isIn: [_.values(PROJECT_TYPE)]
27+
}
28+
},
29+
status: {
30+
type: DataTypes.STRING,
31+
allowNull: false,
32+
validate: {
33+
isIn: [_.values(PROJECT_STATUS)]
34+
}
35+
},
36+
details: { type: DataTypes.JSON },
37+
challengeEligibility: DataTypes.JSON,
38+
deletedAt: { type: DataTypes.DATE, allowNull: true },
39+
createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
40+
updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
41+
createdBy: { type: DataTypes.INTEGER, allowNull: false },
42+
updatedBy: { type: DataTypes.INTEGER, allowNull: false }
43+
}, {
44+
tableName: 'projects',
45+
timestamps: true,
46+
updatedAt: 'updatedAt',
47+
createdAt: 'createdAt',
48+
deletedAt: 'deletedAt',
49+
indexes: [
50+
{ fields: ['createdAt'] },
51+
{ fields: ['name'] },
52+
{ fields: ['type'] },
53+
{ fields: ['status'] },
54+
{ fields: ['directProjectId'] }
55+
],
56+
classMethods: {
57+
/*
58+
* @Co-pilots should be able to view projects any of the following conditions are met:
59+
* a. they are registered active project members on the project
60+
* b. any project that is in 'reviewed' state AND does not yet have a co-pilot assigned
61+
* @param userId the id of user
62+
*/
63+
getProjectIdsForCopilot: function(userId) {
64+
return this.findAll({
65+
where: {
66+
$or: [
67+
['EXISTS(SELECT * FROM "project_members" WHERE "deletedAt" IS NULL AND "projectId" = "Project".id AND "userId" = ? )', userId],
68+
['"Project".status=? AND NOT EXISTS(SELECT * FROM "project_members" WHERE "deletedAt" IS NULL AND "projectId" = "Project".id AND "role" = ? )',
69+
PROJECT_STATUS.REVIEWED, PROJECT_MEMBER_ROLE.COPILOT]
70+
]
71+
},
72+
attributes:['id'],
73+
raw: true
74+
})
75+
.then((res) => {
76+
return _.map(res, 'id')
77+
})
78+
},
79+
/**
80+
* Get direct project id
81+
* @param id the id of project
82+
*/
83+
getDirectProjectId: function(id) {
84+
return this.findById(id, {
85+
attributes:['directProjectId'],
86+
raw: true
87+
})
88+
.then((res) => {
89+
return res.directProjectId
90+
})
91+
},
92+
associate: (models) => {
93+
Project.hasMany(models.ProjectMember, { as : 'members', foreignKey: 'projectId' })
94+
Project.hasMany(models.ProjectAttachment, { as : 'attachments', foreignKey: 'projectId' })
95+
},
96+
/**
97+
* Search keyword in name, description, details.utm.code
98+
* @param parameters the parameters
99+
* - filters: the filters contains keyword
100+
* - order: the order
101+
* - limit: the limit
102+
* - offset: the offset
103+
* - attributes: the attributes to get
104+
* @param log the request log
105+
* @return the result rows and count
106+
*/
107+
searchText: function(parameters, log) {
108+
// special handling for keyword filter
109+
var query = '1=1 ';
110+
if (_.has(parameters.filters, 'id')) {
111+
if (_.isObject(parameters.filters.id)) {
112+
if (parameters.filters.id['$in'].length === 0) {
113+
parameters.filters.id['$in'].push(-1)
114+
}
115+
query += `AND id IN (${parameters.filters.id['$in']}) `;
116+
} else if(_.isString(parameters.filters.id) || _.isNumber(parameters.filters.id)){
117+
query += `AND id = ${parameters.filters.id} `;
118+
}
119+
}
120+
if (_.has(parameters.filters, 'status')) {
121+
query += `AND status = '${parameters.filters.status}' `;
122+
}
123+
if (_.has(parameters.filters, 'type')) {
124+
query += `AND type = '${parameters.filters.type}' `;
125+
}
126+
if (_.has(parameters.filters, 'keyword')) {
127+
query += `AND "projectFullText" ~ lower('${parameters.filters.keyword}')`;
128+
}
129+
130+
let attributesStr = '"' + parameters.attributes.join('","') + '"';
131+
let orderStr = '"' + parameters.order[0][0] + '" ' + parameters.order[0][1];
132+
133+
// select count of projects
134+
return sequelize.query(`SELECT COUNT(1) FROM projects WHERE ${query}`,
135+
{ type: sequelize.QueryTypes.SELECT,
136+
logging: (str) => { log.debug(str); },
137+
raw: true
138+
})
139+
.then(function(count) {
140+
count = count[0].count
141+
// select project attributes
142+
return sequelize.query(`SELECT ${attributesStr} FROM projects WHERE ${query} ORDER BY ${orderStr} LIMIT ${parameters.limit} OFFSET ${parameters.offset}`,
143+
{ type: sequelize.QueryTypes.SELECT,
144+
logging: (str) => { log.debug(str); },
145+
raw: true
146+
})
147+
.then(function(projects) {
148+
return {rows: projects, count: count};
149+
});
150+
});
151+
}
152+
}
153+
})
154+
155+
return Project
156+
}

0 commit comments

Comments
 (0)