diff --git a/README.md b/README.md
index 66a3cab04..6cfc55390 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,7 @@ Please get in touch via [@DashboardHub](https://twitter.com/DashboardHub) and le
2. Enter the 2 OAuth private keys from GitHub into the Firebase Authentication
3. Click **Databases** and create an empty `firestore` database (indexes, security, collections and rules will all be automatically created later on as part of the deployment)
4. Update `{{ FIREBASE_FUNCTIONS_URL }}` in file `functions/src/environments/environment.ts` with your function subdomain, for example `us-central1-pipelinedashboard-test`
+4. Update `{{ GITHUB_WEBHOOK_SECRET }}` in file `functions/src/environments/environment.ts` with your private secret key (random string), this is used to protect your webhook function, for example `pipelinedashboard-test-123`
#### Angular
diff --git a/functions/package-lock.json b/functions/package-lock.json
index a1c661851..1ce79f4be 100644
--- a/functions/package-lock.json
+++ b/functions/package-lock.json
@@ -892,6 +892,15 @@
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz",
"integrity": "sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw=="
},
+ "@types/uuid": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.3.tgz",
+ "integrity": "sha512-5fRLCYhLtDb3hMWqQyH10qtF+Ud2JnNCXTCZ+9ktNdCcgslcuXkDTkFcJNk++MT29yDntDnlF1+jD+uVGumsbw==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -1423,7 +1432,7 @@
},
"readable-stream": {
"version": "2.3.6",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
@@ -1437,7 +1446,7 @@
},
"string_decoder": {
"version": "1.1.1",
- "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
@@ -2592,7 +2601,7 @@
},
"path-is-absolute": {
"version": "1.0.1",
- "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
@@ -3240,9 +3249,9 @@
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
- "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+ "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
},
"vary": {
"version": "1.1.2",
diff --git a/functions/package.json b/functions/package.json
index bd3040747..9f6db9e0f 100644
--- a/functions/package.json
+++ b/functions/package.json
@@ -20,12 +20,13 @@
"firebase-functions": "^3.1.0",
"request": "^2.88.0",
"request-promise-native": "^1.0.7",
- "uuid": "^3.3.2",
+ "uuid": "^3.3.3",
"winston": "^3.2.1"
},
"devDependencies": {
"@types/cors": "^2.8.5",
"@types/request-promise-native": "^1.0.16",
+ "@types/uuid": "3.4.3",
"firebase-functions-test": "^0.1.6",
"tslint": "~5.16.0",
"typescript": "^3.4.5"
diff --git a/functions/src/client/firebase-admin.ts b/functions/src/client/firebase-admin.ts
index 657d0994c..1276acae6 100644
--- a/functions/src/client/firebase-admin.ts
+++ b/functions/src/client/firebase-admin.ts
@@ -8,6 +8,14 @@ export declare type WriteResult = admin.firestore.WriteResult;
export declare type QuerySnapshot = admin.firestore.QuerySnapshot;
export declare type QueryDocumentSnapshot = admin.firestore.QueryDocumentSnapshot;
export declare type DocumentReference = admin.firestore.DocumentReference;
+export declare type Transaction = admin.firestore.Transaction;
+export declare type WriteBatch = admin.firestore.WriteBatch;
export declare type FieldValue = admin.firestore.FieldValue;
+export declare type CollectionReference = admin.firestore.CollectionReference;
+export declare type Query = admin.firestore.Query;
+
+// tslint:disable-next-line: typedef
+export const FieldPath = admin.firestore.FieldPath;
export const IncrementFieldValue: FieldValue = admin.firestore.FieldValue.increment(1);
+
diff --git a/functions/src/environments/environment.ts b/functions/src/environments/environment.ts
index 0057c661e..92079ed85 100644
--- a/functions/src/environments/environment.ts
+++ b/functions/src/environments/environment.ts
@@ -2,9 +2,60 @@
export const enviroment: Config = {
githubWebhook: {
url: 'https://{{ FIREBASE_FUNCTIONS_URL }}.cloudfunctions.net/responseGitWebhookRepository',
+ secret: '{{ GITHUB_WEBHOOK_SECRET }}',
+ content_type: 'json',
+ insecure_ssl: '0',
events: [
- 'push',
+ // IMPLEMENTED
+ 'create',
+ 'issue_comment',
+ 'issues',
+ 'member',
+ 'milestone',
'pull_request',
+ 'push',
+ 'release',
+ 'repository',
+ 'status',
+ 'watch',
+
+ // NOT IMPLEMENTED
+ 'check_run',
+ 'check_suite',
+ 'commit_comment',
+ 'delete',
+ 'deploy_key',
+ 'deployment',
+ 'deployment_status',
+ 'fork',
+ 'gollum',
+ 'label',
+ 'meta',
+ 'page_build',
+ 'project_card',
+ 'project_column',
+ 'project',
+ 'public',
+ 'pull_request_review',
+ 'pull_request_review_comment',
+ 'registry_package',
+ 'repository_import',
+ 'repository_vulnerability_alert',
+ 'star',
+ 'team_add',
+
+ // NOT ALLOW for this hook
+ // 'content_reference',
+ // 'github_app_authorization',
+ // 'installation',
+ // 'installation_repositories',
+ // 'marketplace_purchase',
+ // 'membership',
+ // 'organization',
+ // 'org_block',
+ // 'repository_dispatch',
+ // 'security_advisory',
+ // 'team',
],
},
@@ -13,6 +64,9 @@ export const enviroment: Config = {
interface Config {
githubWebhook: {
url: string,
+ secret: string,
+ content_type: 'json' | 'form',
+ insecure_ssl: '0' | '1',
events: string[],
}
}
diff --git a/functions/src/index.ts b/functions/src/index.ts
index 9a49e4467..716b03970 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -13,17 +13,19 @@ import { onResponseGitWebhookRepository } from './repository/response-git-webhoo
import { onUpdateRepository } from './repository/update-repository';
// Dashboard users
+import { onCreateUser } from './user/create-user';
import { getUserEvents, EventsInput } from './user/events';
import { getUserRepos, ReposInput } from './user/repos';
import { onUpdateUserStats } from './user/stats';
+import { onUpdateUser } from './user/update-user';
// Dashboard projects
import { deleteMonitorPings, ping, MonitorInfoInput } from './monitor/monitor';
-import { onDeleteProject, onDeleteProjectRepositories } from './project/delete-project';
-import { onUpdateProjectRepositories } from './project/update-repositories';
+import { onDeleteProject } from './project/delete-project';
+import { onUpdateProject } from './project/update-project';
import { onDeleteGitWebhookRepository, DeleteGitWebhookRepositoryInput } from './repository/delete-git-webhook-repository';
-import { onCreatePings, onCreateProject, onCreateUser } from './application/stats';
+import { onCreatePings as onCreatePingsStats, onCreateProject as onCreateProjectStats, onCreateUser as onCreateUserStats } from './application/stats';
import { updateViews, ProjectInput } from './project/project';
import { deletePingsAfter30days, runAllMonitors60Mins } from './scheduler/schedule';
@@ -34,23 +36,24 @@ declare type Change = functions.Change;
export const findAllUserRepositories: HttpsFunction = functions.https.onCall((input: ReposInput, context: CallableContext) => getUserRepos(input.token, context.auth.uid));
export const findAllUserEvents: HttpsFunction = functions.https.onCall((input: EventsInput, context: CallableContext) => getUserEvents(input.token, context.auth.uid, input.username));
-export const findRepositoryInfo: HttpsFunction = functions.https.onCall((input: RepositoryInfoInput, context: CallableContext) => getRepositoryInfo(input.token, input.fullName));
+export const findRepositoryInfo: HttpsFunction = functions.https.onCall((input: RepositoryInfoInput, context: CallableContext) => getRepositoryInfo(input.token, input.repository));
export const createGitWebhookRepository: HttpsFunction = functions.https.onCall((input: CreateGitWebhookRepositoryInput, context: CallableContext) => onCreateGitWebhookRepository(input.token, input.repositoryUid));
-export const deleteGitWebhookRepository: HttpsFunction = functions.https.onCall((input: DeleteGitWebhookRepositoryInput, context: CallableContext) => onDeleteGitWebhookRepository(input.token, input.repositoryUid));
+export const deleteGitWebhookRepository: HttpsFunction = functions.https.onCall((input: DeleteGitWebhookRepositoryInput, context: CallableContext) => onDeleteGitWebhookRepository(input.token, input.data));
export const responseGitWebhookRepository: HttpsFunction = onResponseGitWebhookRepository;
export const pingMonitor: HttpsFunction = functions.https.onCall((input: MonitorInfoInput, context: CallableContext) => ping(input.projectUid, input.monitorUid, input.type));
export const deletePingsByMonitor: HttpsFunction = functions.https.onCall((input: MonitorInfoInput, context: CallableContext) => deleteMonitorPings(input.projectUid, input.monitorUid));
export const updateProjectViews: HttpsFunction = functions.https.onCall((input: ProjectInput, context: CallableContext) => updateViews(input.projectUid));
-export const deletePingsByProject: CloudFunction = onDeleteProject;
-export const deleteProjectRepositories: CloudFunction = onDeleteProjectRepositories;
-export const updateProjectRepositories: CloudFunction = onUpdateProjectRepositories;
+export const deleteProject: CloudFunction = onDeleteProject;
+export const updateProject: CloudFunction = onUpdateProject;
export const updateRepository: CloudFunction> = onUpdateRepository;
export const createRepository: CloudFunction = onCreateRepository;
export const updateUserStats: CloudFunction = onUpdateUserStats;
export const delete30DaysPings: CloudFunction = deletePingsAfter30days;
export const runPings60Mins: CloudFunction = runAllMonitors60Mins;
-
-export const createProject: CloudFunction = onCreateProject;
-export const createPing: CloudFunction = onCreatePings;
export const createUser: CloudFunction = onCreateUser;
+export const updateUser: CloudFunction> = onUpdateUser;
+
+export const createProjectStat: CloudFunction = onCreateProjectStats;
+export const createPingsStats: CloudFunction = onCreatePingsStats;
+export const createUserStats: CloudFunction = onCreateUserStats;
diff --git a/functions/src/mappers/github/event.mapper.ts b/functions/src/mappers/github/event.mapper.ts
index 99e04defe..beb6c83ab 100644
--- a/functions/src/mappers/github/event.mapper.ts
+++ b/functions/src/mappers/github/event.mapper.ts
@@ -1,7 +1,6 @@
-// Third party modules
import { firestore } from 'firebase-admin';
-// Dashboard hub firebase functions mappers/modesl
+// Dashboard mappers/models
import { GitHubEventType } from './event.mapper';
import { GitHubOrganisationtInput, GitHubOrganisationMapper, GitHubOrganisationModel } from './organisation.mapper';
import { GitHubPayloadInput, GitHubPayloadMapper, GitHubPayloadModel } from './payload.mapper';
@@ -13,18 +12,18 @@ export type GitHubEventType = 'PullRequestEvent' | 'IssueCommentEvent' | 'Create
export interface GitHubEventInput {
id: string;
type: GitHubEventType;
- public: string;
+ public: boolean;
actor: GitHubUserInput;
repo: GitHubRepositoryInput;
org: GitHubOrganisationtInput;
payload: GitHubPayloadInput;
- created_at: firestore.Timestamp;
+ created_at: string;
}
export interface GitHubEventModel {
- uid: string;
+ uid?: string;
type: GitHubEventType;
- public: string;
+ public: boolean;
actor: GitHubUserModel;
repository: GitHubRepositoryModel;
organisation?: GitHubOrganisationModel;
@@ -41,7 +40,7 @@ export class GitHubEventMapper {
actor: GitHubUserMapper.import(input.actor),
repository: GitHubRepositoryMapper.import(input.repo, 'event'),
payload: GitHubPayloadMapper.import(input.type, input.payload),
- createdOn: input.created_at,
+ createdOn: firestore.Timestamp.fromDate(new Date(input.created_at)),
};
if (input.org) {
diff --git a/functions/src/mappers/github/issue.mapper.ts b/functions/src/mappers/github/issue.mapper.ts
index 1b19a7e93..4aa61da5f 100644
--- a/functions/src/mappers/github/issue.mapper.ts
+++ b/functions/src/mappers/github/issue.mapper.ts
@@ -1,11 +1,10 @@
-// Third party modules
import { firestore } from 'firebase-admin';
-// Dashboard hub firebase functions mappers/models
+// Dashboard mappers/models
import { GitHubUserInput, GitHubUserMapper, GitHubUserModel } from './user.mapper';
export interface GitHubIssueInput {
- id: string;
+ id: number;
html_url: string;
state: string;
title: string;
@@ -13,12 +12,12 @@ export interface GitHubIssueInput {
body: string;
user: GitHubUserInput;
assignees: GitHubUserInput[];
- created_at: firestore.Timestamp;
- updated_at: firestore.Timestamp;
+ created_at: string;
+ updated_at: string;
}
export interface GitHubIssueModel {
- uid: string;
+ uid: number;
url: string;
state: string;
title: string;
@@ -41,8 +40,8 @@ export class GitHubIssueMapper {
description: input.body,
owner: GitHubUserMapper.import(input.user),
assignees: input.assignees.map((assignee: GitHubUserInput) => GitHubUserMapper.import(assignee)),
- createdOn: input.created_at,
- updatedOn: input.updated_at,
+ createdOn: firestore.Timestamp.fromDate(new Date(input.created_at)),
+ updatedOn: firestore.Timestamp.fromDate(new Date(input.updated_at)),
};
}
}
diff --git a/functions/src/mappers/github/milestone.mapper.ts b/functions/src/mappers/github/milestone.mapper.ts
index 490998d6d..f64ed293d 100644
--- a/functions/src/mappers/github/milestone.mapper.ts
+++ b/functions/src/mappers/github/milestone.mapper.ts
@@ -1,6 +1,10 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
import { GitHubUserInput, GitHubUserMapper, GitHubUserModel } from './index.mapper';
export interface GitHubMilestoneInput {
+ id: number;
title: string;
creator: GitHubUserInput;
state: string;
@@ -12,6 +16,7 @@ export interface GitHubMilestoneInput {
}
export interface GitHubMilestoneModel {
+ uid: number;
title: string;
creator: GitHubUserModel;
state: string;
@@ -19,12 +24,13 @@ export interface GitHubMilestoneModel {
closeIssues: number;
htmlUrl: string;
description: string;
- updatedAt: string;
+ updatedAt: firestore.Timestamp;
}
export class GitHubMilestoneMapper {
static import(input: GitHubMilestoneInput): GitHubMilestoneModel {
return {
+ uid: input.id,
title: input.title,
creator: GitHubUserMapper.import(input.creator),
state: input.state,
@@ -32,7 +38,7 @@ export class GitHubMilestoneMapper {
closeIssues: input.closed_issues,
htmlUrl: input.html_url,
description: input.description,
- updatedAt: input.updated_at,
+ updatedAt: firestore.Timestamp.fromDate(new Date(input.updated_at)),
};
}
}
diff --git a/functions/src/mappers/github/payload.mapper.ts b/functions/src/mappers/github/payload.mapper.ts
index 8e7bb479e..1992567aa 100644
--- a/functions/src/mappers/github/payload.mapper.ts
+++ b/functions/src/mappers/github/payload.mapper.ts
@@ -1,7 +1,7 @@
import { GitHubEventType } from './event.mapper';
export interface GitHubPayloadInput {
- title: string;
+ title?: string;
action?: string;
ref?: string;
ref_type?: string;
@@ -15,7 +15,9 @@ export interface GitHubPayloadInput {
body: string;
};
release?: {
- name: string;
+ tag_name: string;
+ target_commitish: string;
+ name?: string;
};
}
@@ -47,7 +49,7 @@ export class GitHubPayloadMapper {
output.title = `${input.ref_type}: ${input.ref}`;
break;
case 'ReleaseEvent':
- output.title = `${input.action}: ${input.release.name}`;
+ output.title = `${input.action}: ${input.release.tag_name}@${input.release.target_commitish}` + (input.release.name ? ` - ${input.release.name}` : '');
break;
case 'WatchEvent':
output.title = `${input.action} watching`;
diff --git a/functions/src/mappers/github/pullRequest.mapper.ts b/functions/src/mappers/github/pullRequest.mapper.ts
index 5bda33669..00d460916 100644
--- a/functions/src/mappers/github/pullRequest.mapper.ts
+++ b/functions/src/mappers/github/pullRequest.mapper.ts
@@ -1,11 +1,10 @@
-// Third party modules
import { firestore } from 'firebase-admin';
-// Dashboard hub firebase functions mappers/models
+// Dashboard mappers/models
import { GitHubUserInput, GitHubUserMapper, GitHubUserModel } from './user.mapper';
export interface GitHubPullRequestInput {
- id: string;
+ id: number;
html_url: string;
state: string;
title: string;
@@ -14,12 +13,12 @@ export interface GitHubPullRequestInput {
user: GitHubUserInput
assignees: GitHubUserInput[];
requested_reviewers: GitHubUserInput[];
- created_at: firestore.Timestamp;
- updated_at: firestore.Timestamp;
+ created_at: string;
+ updated_at: string;
}
export interface GitHubPullRequestModel {
- uid: string;
+ uid: number;
url: string;
state: string;
title: string;
@@ -44,8 +43,8 @@ export class GitHubPullRequestMapper {
owner: GitHubUserMapper.import(input.user),
assignees: input.assignees.map((assignee: GitHubUserInput) => GitHubUserMapper.import(assignee)),
reviewers: input.requested_reviewers.map((reviewer: GitHubUserInput) => GitHubUserMapper.import(reviewer)),
- createdOn: input.created_at,
- updatedOn: input.updated_at,
+ createdOn: firestore.Timestamp.fromDate(new Date(input.created_at)),
+ updatedOn: firestore.Timestamp.fromDate(new Date(input.updated_at)),
};
}
}
diff --git a/functions/src/mappers/github/release.mapper.ts b/functions/src/mappers/github/release.mapper.ts
index aed8562a5..c2ca99eb3 100644
--- a/functions/src/mappers/github/release.mapper.ts
+++ b/functions/src/mappers/github/release.mapper.ts
@@ -1,25 +1,26 @@
-// Third party modules
import { firestore } from 'firebase-admin';
-// Dashboard hub firebase functions mappers/models
+// Dashboard mappers/models
import { GitHubUserInput, GitHubUserMapper, GitHubUserModel } from './user.mapper';
export interface GitHubReleaseInput {
- id: string;
+ id: number;
name: string;
body: string;
author: GitHubUserInput;
html_url: string;
- published_at: firestore.Timestamp;
+ published_at: string;
+ prerelease: boolean;
}
export interface GitHubReleaseModel {
- uid: string;
+ uid: number;
title: string;
description: string;
owner: GitHubUserModel;
htmlUrl: string;
createdOn: firestore.Timestamp;
+ isPrerelease: boolean;
}
export class GitHubReleaseMapper {
@@ -30,7 +31,30 @@ export class GitHubReleaseMapper {
description: input.body,
owner: GitHubUserMapper.import(input.author),
htmlUrl: input.html_url,
- createdOn: input.published_at,
+ createdOn: firestore.Timestamp.fromDate(new Date(input.published_at)),
+ isPrerelease: input.prerelease,
};
}
+
+ public static sortReleaseList(releases: GitHubReleaseModel[]): GitHubReleaseModel[] {
+ return releases
+ .sort(
+ (a: GitHubReleaseModel, b: GitHubReleaseModel): number => {
+ // tslint:disable-next-line: triple-equals
+ if (a.createdOn == null && b.createdOn == null) {
+ return 0;
+ }
+ // tslint:disable-next-line: triple-equals
+ if (a.createdOn == null) {
+ return 1;
+ }
+ // tslint:disable-next-line: triple-equals
+ if (b.createdOn == null) {
+ return -1;
+ }
+ return b.createdOn.toMillis() - a.createdOn.toMillis();
+ }
+ )
+ ;
+ }
}
diff --git a/functions/src/mappers/github/repository.mapper.ts b/functions/src/mappers/github/repository.mapper.ts
index 5373a112a..c585005f1 100644
--- a/functions/src/mappers/github/repository.mapper.ts
+++ b/functions/src/mappers/github/repository.mapper.ts
@@ -11,24 +11,26 @@ import { GitHubReleaseModel } from './release.mapper';
import { GitHubRepositoryWebhookModel } from './webhook.mapper';
export interface GitHubRepositoryInput {
- id: string;
- uid: string;
- name?: string;
- full_name?: string;
+ id: number;
+ name: string;
+ full_name: string;
description?: string;
url: string;
private: boolean;
- fork: string;
+ fork: boolean;
+ forks_count: number;
+ stargazers_count: number;
+ watchers_count: number;
}
export interface GitHubRepositoryModel {
- id: string;
- uid: string;
- fullName?: string;
+ id: number;
+ uid?: string;
+ fullName: string;
description?: string;
url: string;
private: boolean;
- fork: string;
+ fork: boolean;
pullRequests?: GitHubPullRequestModel[];
events?: GitHubEventModel[];
releases?: GitHubReleaseModel[];
@@ -37,31 +39,32 @@ export interface GitHubRepositoryModel {
milestones?: GitHubMilestoneModel[];
updatedAt: firestore.Timestamp;
webhook?: GitHubRepositoryWebhookModel;
+ forksCount: number;
+ stargazersCount: number;
+ watchersCount: number;
}
export class GitHubRepositoryMapper {
- static fullNameToUid(fullName: string) {
- return fullName.replace('/', '+');
- }
-
static import(input: GitHubRepositoryInput, type: 'minimum' | 'all' | 'event' = 'minimum'): GitHubRepositoryModel {
const output: any = {};
-
if (type === 'all') {
- output.fork = input.fork;
+ output.fork = input.fork;
+ output.forksCount = input.forks_count;
+ output.stargazersCount = input.stargazers_count;
+ output.watchersCount = input.watchers_count;
}
if (type === 'event' || type === 'all') {
- output.id = input.id;
- output.fullName = input.name;
- output.url = input.url;
+ output.id = input.id;
+ output.fullName = input.name;
+ output.url = input.url;
}
if (type === 'minimum' || type === 'all') {
- output.uid = GitHubRepositoryMapper.fullNameToUid(input.full_name);
- output.fullName = input.full_name;
- output.description = input.description;
- output.private = input.private;
+ output.id = input.id;
+ output.fullName = input.full_name;
+ output.description = input.description;
+ output.private = input.private;
}
return output;
diff --git a/functions/src/mappers/github/webhook-event-response/create.ts b/functions/src/mappers/github/webhook-event-response/create.ts
new file mode 100644
index 000000000..322960a34
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/create.ts
@@ -0,0 +1,58 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
+import { GitHubEventModel, GitHubEventType } from '../event.mapper';
+import { GitHubPayloadInput, GitHubPayloadMapper } from '../payload.mapper';
+import { GitHubRepositoryMapper } from '../repository.mapper';
+import { GitHubUserMapper } from '../user.mapper';
+import { isExistProperties, HubEventActions, Repository, User } from './shared';
+
+export interface CreateEventInput {
+ ref: string;
+ ref_type: 'branch' | 'tag';
+ master_branch: string;
+ description?: any;
+ pusher_type: string;
+ repository: Repository;
+ sender: User;
+}
+
+export class CreateEventModel implements CreateEventInput, HubEventActions {
+ ref: string;
+ ref_type: 'branch' | 'tag';
+ master_branch: string;
+ description?: any;
+ pusher_type: string;
+ repository: Repository;
+ sender: User;
+
+ constructor(input: CreateEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['ref', 'ref_type', 'master_branch', 'pusher_type', 'repository', 'sender'];
+ return isExistProperties(input, requireKeys);
+ }
+
+ convertToHubEvent(): GitHubEventModel {
+ const eventType: GitHubEventType = 'CreateEvent';
+ const payload: GitHubPayloadInput = {
+ // title: `Created ${this.ref_type} '${this.ref}'.` + this.description ? ` ${this.description}` : '',
+ ref: this.ref,
+ ref_type: this.ref_type,
+ }
+
+ const data: GitHubEventModel = {
+ type: eventType,
+ public: true, // TODO where get
+ actor: GitHubUserMapper.import(this.sender),
+ repository: GitHubRepositoryMapper.import(this.repository, 'event'),
+ payload: GitHubPayloadMapper.import(eventType, payload),
+ createdOn: firestore.Timestamp.now(),
+ };
+
+ return data;
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/index.ts b/functions/src/mappers/github/webhook-event-response/index.ts
new file mode 100644
index 000000000..fd9f0e056
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/index.ts
@@ -0,0 +1,11 @@
+export * from './issues';
+export * from './milestone';
+export * from './pull-request';
+export * from './release';
+export * from './repository';
+export * from './watch';
+export * from './create';
+export * from './push';
+export * from './issue-comment';
+export * from './member';
+export * from './status';
diff --git a/functions/src/mappers/github/webhook-event-response/issue-comment.ts b/functions/src/mappers/github/webhook-event-response/issue-comment.ts
new file mode 100644
index 000000000..2af930c4b
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/issue-comment.ts
@@ -0,0 +1,69 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
+import { GitHubEventModel, GitHubEventType } from '../event.mapper';
+import { GitHubPayloadInput, GitHubPayloadMapper } from '../payload.mapper';
+import { GitHubRepositoryMapper } from '../repository.mapper';
+import { GitHubUserMapper } from '../user.mapper';
+import { isExistProperties, HubEventActions, Issue, Repository, User } from './shared';
+
+interface Comment {
+ url: string;
+ html_url: string;
+ issue_url: string;
+ id: number;
+ node_id: string;
+ user: User;
+ created_at: string;
+ updated_at: string;
+ author_association: string;
+ body: string;
+}
+
+type Action = 'created' | 'edited' | 'deleted';
+
+export interface IssueCommentEventInput {
+ action: Action;
+ issue: Issue;
+ comment: Comment;
+ repository: Repository;
+ sender: User;
+}
+
+export class IssueCommentEventModel implements IssueCommentEventInput, HubEventActions {
+ action: Action;
+ issue: Issue;
+ comment: Comment;
+ repository: Repository;
+ sender: User;
+
+ constructor(input: IssueCommentEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['action', 'issue', 'comment', 'repository', 'sender'];
+ return isExistProperties(input, requireKeys);
+ }
+
+ convertToHubEvent(): GitHubEventModel {
+ const eventType: GitHubEventType = 'IssueCommentEvent';
+ const payload: GitHubPayloadInput = {
+ action: this.action,
+ comment: this.comment,
+ };
+
+ const data: GitHubEventModel = {
+ type: eventType,
+ public: true, // TODO where get
+ actor: GitHubUserMapper.import(this.sender),
+ repository: GitHubRepositoryMapper.import(this.repository, 'event'),
+ payload: GitHubPayloadMapper.import(eventType, payload),
+ createdOn: firestore.Timestamp.now(),
+ };
+
+ return data;
+ }
+
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/issues.ts b/functions/src/mappers/github/webhook-event-response/issues.ts
new file mode 100644
index 000000000..56b735590
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/issues.ts
@@ -0,0 +1,141 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
+import { DocumentData } from '../../../client/firebase-admin';
+import { GitHubEventModel, GitHubEventType } from '../event.mapper';
+import { GitHubIssueModel } from '../issue.mapper';
+import { GitHubPayloadInput, GitHubPayloadMapper } from '../payload.mapper';
+import { GitHubRepositoryMapper } from '../repository.mapper';
+import { GitHubUserMapper } from '../user.mapper';
+import { isExistProperties, HubEventActions, Issue, Repository, User } from './shared';
+
+type Action = 'opened' | 'edited' | 'deleted' | 'transferred' | 'pinned' | 'unpinned' | 'closed' | 'reopened' | 'assigned' | 'unassigned' | 'labeled' | 'unlabeled' | 'locked' | 'unlocked' | 'milestoned' | 'demilestoned';
+
+export interface IssuesEventInput {
+ action: Action;
+ issue: Issue;
+ changes?: any;
+ repository: Repository;
+ sender: User;
+}
+
+export class IssuesEventModel implements IssuesEventInput, HubEventActions {
+ action: Action;
+ issue: Issue;
+ changes?: any;
+ repository: Repository;
+ sender: User;
+
+ constructor(input: IssuesEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['action', 'issue', 'repository', 'sender'];
+ return isExistProperties(input, requireKeys);
+ }
+
+ convertToHubEvent(): GitHubEventModel {
+ const eventType: GitHubEventType = 'IssuesEvent';
+ const payload: GitHubPayloadInput = {
+ action: this.action,
+ issue: this.issue,
+ };
+
+ const data: GitHubEventModel = {
+ type: eventType,
+ public: true, // TODO where get
+ actor: GitHubUserMapper.import(this.sender),
+ repository: GitHubRepositoryMapper.import(this.repository, 'event'),
+ payload: GitHubPayloadMapper.import(eventType, payload),
+ createdOn: firestore.Timestamp.now(),
+ };
+
+ return data;
+ }
+
+
+ updateData(repository: DocumentData): void {
+
+ if (!Array.isArray(repository.issues)) {
+ repository.issues = [];
+ }
+
+ switch (this.action) {
+ case 'opened': {
+ this.opened(repository);
+ break;
+ }
+
+ case 'pinned':
+ case 'unpinned':
+ case 'reopened':
+ case 'assigned':
+ case 'unassigned':
+ case 'labeled':
+ case 'unlabeled':
+ case 'locked':
+ case 'unlocked':
+ case 'milestoned':
+ case 'demilestoned':
+ case 'transferred':
+ case 'edited': {
+ this.edited(repository);
+ break;
+ }
+
+ case 'closed':
+ case 'deleted': {
+ this.deleted(repository);
+ break;
+ }
+
+ default: {
+ throw new Error('Not found action');
+ }
+ }
+
+ }
+
+ private getModel(): GitHubIssueModel {
+ return {
+ uid: this.issue.id,
+ url: this.issue.html_url,
+ state: this.issue.state,
+ title: this.issue.title,
+ number: this.issue.number,
+ description: this.issue.body,
+ owner: GitHubUserMapper.import(this.issue.user),
+ assignees: this.issue.assignees.map((assignee: User) => GitHubUserMapper.import(assignee)),
+ createdOn: firestore.Timestamp.fromDate(new Date(this.issue.created_at)),
+ updatedOn: firestore.Timestamp.fromDate(new Date(this.issue.updated_at)),
+ }
+ }
+
+ private opened(repository: DocumentData): void {
+
+ const issue: GitHubIssueModel = this.getModel();
+
+ repository.issues.unshift(issue);
+ }
+
+
+ private edited(repository: DocumentData): void {
+ const foundIndex: number = repository.issues.findIndex((elem: GitHubIssueModel) => elem.uid === this.issue.id);
+ if (foundIndex > -1) {
+ repository.issues[foundIndex] = this.getModel();
+ } else {
+ this.opened(repository);
+ }
+ }
+
+
+ private deleted(repository: DocumentData): void {
+ if (!Array.isArray(repository.issues)) {
+ return;
+ }
+
+ repository.issues = repository.issues.filter((item: GitHubIssueModel) => item.uid !== this.issue.id);
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/member.ts b/functions/src/mappers/github/webhook-event-response/member.ts
new file mode 100644
index 000000000..32c8bdde5
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/member.ts
@@ -0,0 +1,56 @@
+import { isExistProperties, Repository, User } from './shared';
+
+interface Member {
+ login: string;
+ id: number;
+ node_id: string;
+ avatar_url: string;
+ gravatar_id: string;
+ url: string;
+ html_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ starred_url: string;
+ subscriptions_url: string;
+ organizations_url: string;
+ repos_url: string;
+ events_url: string;
+ received_events_url: string;
+ type: string;
+ site_admin: boolean;
+}
+
+export interface MemberEventInput {
+ action: 'added' | 'deleted' | 'edited';
+ member: Member;
+ repository: Repository;
+ sender: User;
+ changes?: { old_permission: { from: string } };
+}
+
+export class MemberEventModel implements MemberEventInput {
+ action: 'added' | 'deleted' | 'edited';
+ member: Member;
+ repository: Repository;
+ sender: User;
+ changes?: { old_permission: { from: string } };
+
+ constructor(input: MemberEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['action', 'member', 'repository', 'sender'];
+
+ const objKeys: string[] = Object.keys(input);
+ let length: number = requireKeys.length;
+
+ if (objKeys.find((elem: string) => elem === 'changes')) {
+ ++length;
+ }
+
+ return objKeys.length === length && isExistProperties(input, requireKeys);
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/milestone.ts b/functions/src/mappers/github/webhook-event-response/milestone.ts
new file mode 100644
index 000000000..8a6ac61e3
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/milestone.ts
@@ -0,0 +1,114 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
+import { DocumentData } from '../../../client/firebase-admin';
+import { GitHubMilestoneModel } from '../milestone.mapper';
+import { GitHubUserMapper } from '../user.mapper';
+import { isExistProperties, Milestone, Repository, User } from './shared';
+
+type Action = 'created' | 'closed' | 'opened' | 'edited' | 'deleted';
+
+export interface MilestoneEventInput {
+ action: Action;
+ milestone: Milestone;
+ repository: Repository;
+ sender: User;
+}
+
+export class MilestoneEventModel implements MilestoneEventInput {
+ action: Action;
+ milestone: Milestone;
+ repository: Repository;
+ sender: User;
+
+ constructor(input: MilestoneEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['action', 'milestone', 'repository', 'sender'];
+ return isExistProperties(input, requireKeys);
+ }
+
+ updateData(repository: DocumentData): void {
+
+ if (!Array.isArray(repository.milestones)) {
+ repository.milestones = [];
+ }
+
+ switch (this.action) {
+ case 'created': {
+ this.created(repository);
+ break;
+ }
+ case 'opened': {
+ this.opened(repository);
+ break;
+ }
+ case 'closed': {
+ this.closed(repository);
+ break;
+ }
+ case 'edited': {
+ this.edited(repository);
+ break;
+ }
+ case 'deleted': {
+ this.deleted(repository);
+ break;
+ }
+
+ default: {
+ throw new Error('Not found action');
+ }
+ }
+
+ }
+
+ private getModel(): GitHubMilestoneModel {
+ return {
+ uid: this.milestone.id,
+ title: this.milestone.title,
+ creator: GitHubUserMapper.import(this.milestone.creator),
+ state: this.milestone.state,
+ openIssues: this.milestone.open_issues,
+ closeIssues: this.milestone.closed_issues,
+ htmlUrl: this.milestone.html_url,
+ description: this.milestone.description,
+ updatedAt: firestore.Timestamp.fromDate(new Date(this.milestone.updated_at)),
+ };
+ }
+
+ private created(repository: DocumentData): void {
+
+ const milestone: GitHubMilestoneModel = this.getModel();
+
+ repository.milestones.unshift(milestone);
+ }
+
+ private opened(repository: DocumentData): void {
+ this.edited(repository);
+ }
+
+ private closed(repository: DocumentData): void {
+ this.edited(repository);
+ }
+
+ private edited(repository: DocumentData): void {
+ const foundIndex: number = repository.milestones.findIndex((elem: GitHubMilestoneModel) => elem.uid === this.milestone.id);
+ if (foundIndex > -1) {
+ repository.milestones[foundIndex] = this.getModel();
+ } else {
+ this.created(repository);
+ }
+ }
+
+
+ private deleted(repository: DocumentData): void {
+ if (!Array.isArray(repository.milestones)) {
+ return;
+ }
+ repository.milestones = repository.milestones.filter((item: GitHubMilestoneModel) => item.uid !== this.milestone.id);
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/pull-request.ts b/functions/src/mappers/github/webhook-event-response/pull-request.ts
new file mode 100644
index 000000000..98363b332
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/pull-request.ts
@@ -0,0 +1,207 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
+import { DocumentData } from '../../../client/firebase-admin';
+import { GitHubEventModel, GitHubEventType } from '../event.mapper';
+import { GitHubPayloadInput, GitHubPayloadMapper } from '../payload.mapper';
+import { GitHubPullRequestModel } from '../pullRequest.mapper';
+import { GitHubRepositoryMapper } from '../repository.mapper';
+import { GitHubUserMapper } from '../user.mapper';
+import { isExistProperties, HubEventActions, Repository, User } from './shared';
+
+
+interface InfoRepoObj {
+ label: string;
+ ref: string;
+ sha: string;
+ user: User;
+ repo: Repository;
+}
+
+interface LinkObj {
+ href: string;
+}
+
+interface Links {
+ self: LinkObj;
+ html: LinkObj;
+ issue: LinkObj;
+ comments: LinkObj;
+ review_comments: LinkObj;
+ review_comment: LinkObj;
+ commits: LinkObj;
+ statuses: LinkObj;
+}
+
+interface PullRequest {
+ url: string;
+ id: number;
+ node_id: string;
+ html_url: string;
+ diff_url: string;
+ patch_url: string;
+ issue_url: string;
+ number: number;
+ state: string;
+ locked: boolean;
+ title: string;
+ user: User;
+ body: string;
+ created_at: string;
+ updated_at: string;
+ closed_at?: any;
+ merged_at?: any;
+ merge_commit_sha?: any;
+ assignee?: any;
+ assignees: any[];
+ requested_reviewers: any[];
+ requested_teams: any[];
+ labels: any[];
+ milestone?: any;
+ commits_url: string;
+ review_comments_url: string;
+ review_comment_url: string;
+ comments_url: string;
+ statuses_url: string;
+ head: InfoRepoObj;
+ base: InfoRepoObj;
+ _links: Links;
+ author_association: string;
+ draft: boolean;
+ merged: boolean;
+ mergeable?: any;
+ rebaseable?: any;
+ mergeable_state: string;
+ merged_by?: any;
+ comments: number;
+ review_comments: number;
+ maintainer_can_modify: boolean;
+ commits: number;
+ additions: number;
+ deletions: number;
+ changed_files: number;
+}
+
+type Action = 'assigned' | 'unassigned' | 'review_requested' | 'review_request_removed' | 'labeled' | 'unlabeled' | 'opened' | 'edited' | 'closed' | 'ready_for_review' | 'locked' | 'unlocked' | 'reopened';
+
+export interface PullRequestEventInput {
+ action: Action;
+ number: number;
+ pull_request: PullRequest;
+ repository: Repository;
+ sender: User;
+}
+
+export class PullRequestEventModel implements PullRequestEventInput, HubEventActions {
+ action: Action;
+ number: number;
+ pull_request: PullRequest;
+ repository: Repository;
+ sender: User;
+
+ constructor(input: PullRequestEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['action', 'number', 'pull_request', 'repository', 'sender'];
+ return isExistProperties(input, requireKeys);
+ }
+
+ convertToHubEvent(): GitHubEventModel {
+ const eventType: GitHubEventType = 'PullRequestEvent';
+ const payload: GitHubPayloadInput = {
+ action: this.action,
+ pull_request: this.pull_request,
+ };
+
+ const data: GitHubEventModel = {
+ type: eventType,
+ public: true, // TODO where get
+ actor: GitHubUserMapper.import(this.sender),
+ repository: GitHubRepositoryMapper.import(this.repository, 'event'),
+ payload: GitHubPayloadMapper.import(eventType, payload),
+ createdOn: firestore.Timestamp.now(),
+ };
+
+ return data;
+ }
+
+ updateData(repository: DocumentData): void {
+
+ if (!Array.isArray(repository.pullRequests)) {
+ repository.pullRequests = [];
+ }
+
+ switch (this.action) {
+ case 'opened': {
+ this.opened(repository);
+ break;
+ }
+ case 'closed': {
+ this.closed(repository);
+ break;
+ }
+ case 'assigned':
+ case 'unassigned':
+ case 'review_requested':
+ case 'review_request_removed':
+ case 'labeled':
+ case 'unlabeled':
+ case 'ready_for_review':
+ case 'locked':
+ case 'unlocked':
+ case 'reopened':
+ case 'edited': {
+ this.edited(repository);
+ break;
+ }
+ default: {
+ throw new Error('Not found action');
+ }
+ }
+
+ }
+
+ private getModel(): GitHubPullRequestModel {
+ return {
+ uid: this.pull_request.id,
+ url: this.pull_request.html_url,
+ state: this.pull_request.state,
+ title: this.pull_request.title,
+ description: this.pull_request.body,
+ id: this.pull_request.number,
+ owner: GitHubUserMapper.import(this.pull_request.user),
+ assignees: this.pull_request.assignees.map((assignee: User) => GitHubUserMapper.import(assignee)),
+ reviewers: this.pull_request.requested_reviewers.map((reviewer: User) => GitHubUserMapper.import(reviewer)),
+ createdOn: firestore.Timestamp.fromDate(new Date(this.pull_request.created_at)),
+ updatedOn: firestore.Timestamp.fromDate(new Date(this.pull_request.updated_at)),
+ }
+ }
+
+ private opened(repository: DocumentData): void {
+
+ const pull_request: GitHubPullRequestModel = this.getModel();
+
+ repository.pullRequests.unshift(pull_request);
+ }
+
+
+ private edited(repository: DocumentData): void {
+ const foundIndex: number = repository.pullRequests.findIndex((elem: GitHubPullRequestModel) => elem.uid === this.pull_request.id);
+ if (foundIndex > -1) {
+ repository.pullRequests[foundIndex] = this.getModel();
+ } else {
+ this.opened(repository);
+ }
+ }
+
+
+ private closed(repository: DocumentData): void {
+ if (!Array.isArray(repository.pullRequests)) {
+ return;
+ }
+ repository.pullRequests = repository.pullRequests.filter((item: GitHubPullRequestModel) => item.uid !== this.pull_request.id);
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/push.ts b/functions/src/mappers/github/webhook-event-response/push.ts
new file mode 100644
index 000000000..24f1cec10
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/push.ts
@@ -0,0 +1,74 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
+import { GitHubEventModel, GitHubEventType } from '../event.mapper';
+import { GitHubPayloadInput, GitHubPayloadMapper } from '../payload.mapper';
+import { GitHubRepositoryMapper } from '../repository.mapper';
+import { GitHubUserMapper } from '../user.mapper';
+import { isExistProperties, HubEventActions, Repository, User } from './shared';
+
+interface Pusher {
+ name: string;
+ email: string;
+}
+
+export interface PushEventInput {
+ ref: string;
+ before: string;
+ after: string;
+ created: boolean;
+ deleted: boolean;
+ forced: boolean;
+ base_ref?: any;
+ compare: string;
+ commits: any[];
+ head_commit?: any;
+ repository: Repository;
+ pusher: Pusher;
+ sender: User;
+}
+
+
+export class PushEventModel implements PushEventInput, HubEventActions {
+ ref: string;
+ before: string;
+ after: string;
+ created: boolean;
+ deleted: boolean;
+ forced: boolean;
+ base_ref?: any;
+ compare: string;
+ commits: any[];
+ head_commit?: any;
+ repository: Repository;
+ pusher: Pusher;
+ sender: User;
+
+ constructor(input: PushEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['ref', 'before', 'after', 'created', 'deleted', 'forced', 'base_ref', 'compare', 'commits', 'head_commit', 'repository', 'pusher', 'sender'];
+ return isExistProperties(input, requireKeys);
+ }
+
+ convertToHubEvent(): GitHubEventModel {
+ const eventType: GitHubEventType = 'PushEvent';
+ const payload: GitHubPayloadInput = {
+ ref: this.ref,
+ }
+
+ const data: GitHubEventModel = {
+ type: eventType,
+ public: true, // TODO where get
+ actor: GitHubUserMapper.import(this.sender),
+ repository: GitHubRepositoryMapper.import(this.repository, 'event'),
+ payload: GitHubPayloadMapper.import(eventType, payload),
+ createdOn: firestore.Timestamp.now(),
+ };
+
+ return data;
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/release.ts b/functions/src/mappers/github/webhook-event-response/release.ts
new file mode 100644
index 000000000..a8101ef8a
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/release.ts
@@ -0,0 +1,170 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
+import { DocumentData } from '../../../client/firebase-admin';
+import { GitHubEventModel, GitHubEventType } from '../event.mapper';
+import { GitHubPayloadInput, GitHubPayloadMapper } from '../payload.mapper';
+import { GitHubReleaseMapper, GitHubReleaseModel } from '../release.mapper';
+import { GitHubRepositoryMapper } from '../repository.mapper';
+import { GitHubUserMapper } from '../user.mapper';
+import { isExistProperties, HubEventActions, Repository, User } from './shared';
+
+interface Release {
+ url: string;
+ assets_url: string;
+ upload_url: string;
+ html_url: string;
+ id: number;
+ node_id: string;
+ tag_name: string;
+ target_commitish: string;
+ name?: string;
+ draft: boolean;
+ author: User;
+ prerelease: boolean;
+ created_at: string;
+ published_at: string;
+ assets: any[];
+ tarball_url: string;
+ zipball_url: string;
+ body?: any;
+}
+
+type Action = 'published' | 'unpublished' | 'created' | 'edited' | 'deleted' | 'prereleased';
+
+export interface ReleaseEventInput {
+ action: Action;
+ release: Release;
+ repository: Repository;
+ sender: User;
+}
+
+export class ReleaseEventModel implements ReleaseEventInput, HubEventActions {
+ action: Action;
+ changes?: {
+ body: { from: string },
+ name: { from: string },
+ };
+ release: Release;
+ repository: Repository;
+ sender: User;
+
+ constructor(input: ReleaseEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['action', 'release', 'repository', 'sender'];
+ return isExistProperties(input, requireKeys);
+ }
+
+ convertToHubEvent(): GitHubEventModel {
+ const eventType: GitHubEventType = 'ReleaseEvent';
+ const payload: GitHubPayloadInput = {
+ action: this.action,
+ release: this.release,
+ }
+
+ const data: GitHubEventModel = {
+ type: eventType,
+ public: true, // TODO where get
+ actor: GitHubUserMapper.import(this.sender),
+ repository: GitHubRepositoryMapper.import(this.repository, 'event'),
+ payload: GitHubPayloadMapper.import(eventType, payload),
+ createdOn: firestore.Timestamp.now(),
+ };
+
+ return data;
+ }
+
+ updateData(repository: DocumentData): void {
+
+ if (!Array.isArray(repository.releases)) {
+ repository.releases = [];
+ }
+
+ switch (this.action) {
+ case 'created': {
+ this.created(repository);
+ }
+ case 'published': {
+ this.published(repository);
+ break;
+ }
+
+ case 'unpublished': {
+ this.unpublished(repository);
+ break;
+ }
+
+ case 'edited': {
+ this.edited(repository);
+ break;
+ }
+
+ case 'deleted': {
+ this.deleted(repository);
+ break;
+ }
+
+ case 'prereleased': {
+ this.prereleased(repository);
+ break;
+ }
+
+ default: {
+ throw new Error('Not found action');
+ }
+ }
+
+ GitHubReleaseMapper.sortReleaseList(repository.releases);
+ }
+
+ private getModel(): GitHubReleaseModel {
+ return {
+ uid: this.release.id,
+ title: this.release.name,
+ description: this.release.body,
+ owner: GitHubUserMapper.import(this.release.author),
+ htmlUrl: this.release.html_url,
+ createdOn: firestore.Timestamp.fromDate(new Date(this.release.published_at)),
+ isPrerelease: this.release.prerelease,
+ }
+ }
+
+ private created(repository: DocumentData): void {
+
+ const release: GitHubReleaseModel = this.getModel();
+
+ repository.releases.unshift(release);
+ }
+
+ private published(repository: DocumentData): void {
+ const foundIndex: number = repository.releases.findIndex((elem: GitHubReleaseModel) => elem.uid === this.release.id);
+ if (foundIndex > -1) {
+ repository.releases[foundIndex] = this.getModel();
+ } else {
+ this.created(repository);
+ }
+ }
+
+ private unpublished(repository: DocumentData): void {
+ this.published(repository);
+ }
+
+ private edited(repository: DocumentData): void {
+ this.published(repository);
+ }
+
+ private prereleased(repository: DocumentData): void {
+ this.published(repository);
+ }
+
+ private deleted(repository: DocumentData): void {
+ if (!Array.isArray(repository.releases)) {
+ return;
+ }
+
+ repository.releases = repository.releases.filter((item: GitHubReleaseModel) => item.uid !== this.release.id);
+ }
+}
diff --git a/functions/src/mappers/github/webhook-event-response/repository.ts b/functions/src/mappers/github/webhook-event-response/repository.ts
new file mode 100644
index 000000000..f9ae82320
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/repository.ts
@@ -0,0 +1,118 @@
+import { DocumentData, FieldPath, FirebaseAdmin, Query, QueryDocumentSnapshot, QuerySnapshot, Transaction } from '../../../client/firebase-admin';
+import { RepositoryModel } from '../../../models/index.model';
+import { GitHubRepositoryMapper, GitHubRepositoryModel } from '../repository.mapper';
+import { isExistProperties, Repository, User } from './shared';
+
+type Action = 'created' | 'deleted' | 'archived' | 'unarchived' | 'edited' | 'renamed' | 'transferred' | 'publicized' | 'privatized';
+
+export interface RepositoryEventInput {
+ action: Action;
+ changes?: {
+ repository: {
+ name: { from: string }
+ }
+ };
+ repository: Repository;
+ sender: User;
+}
+
+export class RepositoryEventModel implements RepositoryEventInput {
+ action: Action;
+ changes?: {
+ repository: {
+ name: { from: string }
+ }
+ };
+ repository: Repository;
+ sender: User;
+
+ constructor(input: RepositoryEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['action', 'repository', 'sender'];
+ const objKeys: string[] = Object.keys(input);
+ let length: number = requireKeys.length;
+
+ if (objKeys.find((elem: string) => elem === 'changes')) {
+ ++length;
+ }
+
+ return objKeys.length === length && isExistProperties(input, requireKeys);
+ }
+
+ public async updateData(): Promise {
+
+ switch (this.action) {
+ case 'edited': {
+ await this.edited();
+ break;
+ }
+ case 'renamed': {
+ await this.renamed();
+ break;
+ }
+ case 'transferred': {
+ await this.transferred();
+ break;
+ }
+ case 'publicized': {
+ await this.publicized();
+ break;
+ }
+ case 'privatized': {
+ await this.privatized();
+ break;
+ }
+ }
+
+
+ }
+
+ private async edited(): Promise {
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(this.repository.id);
+ // update repo in users
+ const usersRef: Query = FirebaseAdmin.firestore().collection('users').where(new FieldPath('repositories', 'uids'), 'array-contains', repository.uid);
+ const newMinDataRepo: GitHubRepositoryModel = GitHubRepositoryMapper.import(this.repository);
+ await FirebaseAdmin.firestore().runTransaction((t: Transaction) => {
+ return t.get(usersRef)
+ .then((snap: QuerySnapshot) => {
+ snap.forEach((element: QueryDocumentSnapshot) => {
+ const userData: DocumentData = element.data();
+ if (userData.repositories && Array.isArray(userData.repositories.data) && userData.repositories.data.length > 0) {
+ const repos: GitHubRepositoryModel[] = userData.repositories.data;
+ const foundIndex: number = repos.findIndex((item: GitHubRepositoryModel) => item.uid && item.uid === repository.uid)
+
+ if (foundIndex > -1) {
+ Object.assign(repos[foundIndex], newMinDataRepo);
+ t.update(element.ref, { repositories: { ...userData.repositories, data: repos } });
+ }
+ }
+ });
+
+ });
+ });
+
+ const newDataRepo: GitHubRepositoryModel = GitHubRepositoryMapper.import(this.repository, 'all');
+ Object.assign(repository, newDataRepo);
+ await RepositoryModel.saveRepository(repository);
+ }
+
+ private async renamed(): Promise {
+ await this.edited();
+ }
+
+ private async transferred(): Promise {
+ await this.edited();
+ }
+
+ private async publicized(): Promise {
+ await this.edited();
+ }
+
+ private async privatized(): Promise {
+ await this.edited();
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/shared/functions.ts b/functions/src/mappers/github/webhook-event-response/shared/functions.ts
new file mode 100644
index 000000000..190625285
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/shared/functions.ts
@@ -0,0 +1,24 @@
+import { DocumentData } from "../../../../client/firebase-admin";
+import { HubEventActions } from "./interfaces";
+
+export function isExistProperties(obj: any, keys: string[]){
+ if (!obj) {
+ return false;
+ }
+
+ for (const key of keys) {
+ if (!obj.hasOwnProperty(key)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+export function addHubEventToCollection(repository: DocumentData, event: HubEventActions) {
+ if (!Array.isArray(repository.events)) {
+ repository.events = [];
+ }
+
+ repository.events.unshift(event.convertToHubEvent());
+}
diff --git a/functions/src/mappers/github/webhook-event-response/shared/index.ts b/functions/src/mappers/github/webhook-event-response/shared/index.ts
new file mode 100644
index 000000000..8f06f1cd0
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/shared/index.ts
@@ -0,0 +1,6 @@
+export * from './repository';
+export * from './user';
+export * from './milestone';
+export * from './functions';
+export * from './issue';
+export * from './interfaces';
diff --git a/functions/src/mappers/github/webhook-event-response/shared/interfaces.ts b/functions/src/mappers/github/webhook-event-response/shared/interfaces.ts
new file mode 100644
index 000000000..c0b90fe4c
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/shared/interfaces.ts
@@ -0,0 +1,7 @@
+import { GitHubEventModel } from "../../event.mapper";
+import { Repository } from "./repository";
+
+export interface HubEventActions {
+ convertToHubEvent(): GitHubEventModel;
+ repository: Repository;
+}
diff --git a/functions/src/mappers/github/webhook-event-response/shared/issue.ts b/functions/src/mappers/github/webhook-event-response/shared/issue.ts
new file mode 100644
index 000000000..e758132c7
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/shared/issue.ts
@@ -0,0 +1,38 @@
+import { Milestone } from './milestone';
+import { User } from './user';
+
+interface Label {
+ id: number;
+ node_id: string;
+ url: string;
+ name: string;
+ color: string;
+ default: boolean;
+}
+
+
+export interface Issue {
+ url: string;
+ repository_url: string;
+ labels_url: string;
+ comments_url: string;
+ events_url: string;
+ html_url: string;
+ id: number;
+ node_id: string;
+ number: number;
+ title: string;
+ user: User;
+ labels: Label[];
+ state: string;
+ locked: boolean;
+ assignee: User;
+ assignees: User[];
+ milestone: Milestone;
+ comments: number;
+ created_at: string;
+ updated_at: string;
+ closed_at?: any;
+ author_association: string;
+ body: string;
+}
diff --git a/functions/src/mappers/github/webhook-event-response/shared/milestone.ts b/functions/src/mappers/github/webhook-event-response/shared/milestone.ts
new file mode 100644
index 000000000..87d202321
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/shared/milestone.ts
@@ -0,0 +1,20 @@
+import { User } from './user';
+
+export interface Milestone {
+ url: string;
+ html_url: string;
+ labels_url: string;
+ id: number;
+ node_id: string;
+ number: number;
+ title: string;
+ description: string;
+ creator: User;
+ open_issues: number;
+ closed_issues: number;
+ state: string;
+ created_at: string;
+ updated_at: string;
+ due_on: string;
+ closed_at?: string;
+}
diff --git a/functions/src/mappers/github/webhook-event-response/shared/repository.ts b/functions/src/mappers/github/webhook-event-response/shared/repository.ts
new file mode 100644
index 000000000..a8a8a04f1
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/shared/repository.ts
@@ -0,0 +1,77 @@
+import { User } from './user';
+
+export interface Repository {
+ id: number;
+ node_id: string;
+ name: string;
+ full_name: string;
+ private: boolean;
+ owner: User;
+ html_url: string;
+ description?: any;
+ fork: boolean;
+ url: string;
+ forks_url: string;
+ keys_url: string;
+ collaborators_url: string;
+ teams_url: string;
+ hooks_url: string;
+ issue_events_url: string;
+ events_url: string;
+ assignees_url: string;
+ branches_url: string;
+ tags_url: string;
+ blobs_url: string;
+ git_tags_url: string;
+ git_refs_url: string;
+ trees_url: string;
+ statuses_url: string;
+ languages_url: string;
+ stargazers_url: string;
+ contributors_url: string;
+ subscribers_url: string;
+ subscription_url: string;
+ commits_url: string;
+ git_commits_url: string;
+ comments_url: string;
+ issue_comment_url: string;
+ contents_url: string;
+ compare_url: string;
+ merges_url: string;
+ archive_url: string;
+ downloads_url: string;
+ issues_url: string;
+ pulls_url: string;
+ milestones_url: string;
+ notifications_url: string;
+ labels_url: string;
+ releases_url: string;
+ deployments_url: string;
+ created_at: Date;
+ updated_at: Date;
+ pushed_at: Date;
+ git_url: string;
+ ssh_url: string;
+ clone_url: string;
+ svn_url: string;
+ homepage?: any;
+ size: number;
+ stargazers_count: number;
+ watchers_count: number;
+ language?: string;
+ has_issues: boolean;
+ has_projects: boolean;
+ has_downloads: boolean;
+ has_wiki: boolean;
+ has_pages: boolean;
+ forks_count: number;
+ mirror_url?: any;
+ archived: boolean;
+ disabled: boolean;
+ open_issues_count: number;
+ license?: any;
+ forks: number;
+ open_issues: number;
+ watchers: number;
+ default_branch: string;
+}
diff --git a/functions/src/mappers/github/webhook-event-response/shared/user.ts b/functions/src/mappers/github/webhook-event-response/shared/user.ts
new file mode 100644
index 000000000..d212ae1b0
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/shared/user.ts
@@ -0,0 +1,20 @@
+export interface User {
+ login: string;
+ id: number;
+ node_id: string;
+ avatar_url: string;
+ gravatar_id: string;
+ url: string;
+ html_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ starred_url: string;
+ subscriptions_url: string;
+ organizations_url: string;
+ repos_url: string;
+ events_url: string;
+ received_events_url: string;
+ type: string;
+ site_admin: boolean;
+}
diff --git a/functions/src/mappers/github/webhook-event-response/status.ts b/functions/src/mappers/github/webhook-event-response/status.ts
new file mode 100644
index 000000000..03db737b5
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/status.ts
@@ -0,0 +1,92 @@
+import { isExistProperties, Repository, User } from './shared';
+
+interface Author {
+ name: string;
+ email: string;
+ date: string;
+}
+
+interface Verification {
+ verified: boolean;
+ reason: string;
+ signature: string;
+ payload: string;
+}
+
+interface Commit2 {
+ author: Author;
+ committer: Author;
+ message: string;
+ tree: {
+ sha: string;
+ url: string;
+ };
+ url: string;
+ comment_count: number;
+ verification: Verification;
+}
+
+interface Commit {
+ sha: string;
+ node_id: string;
+ commit: Commit2;
+ url: string;
+ html_url: string;
+ comments_url: string;
+ author: User;
+ committer: User;
+ parents: any[];
+}
+
+
+interface Branch {
+ name: string;
+ commit: {
+ sha: string;
+ url: string;
+ };
+ protected: boolean;
+}
+
+export interface StatusEventInput {
+ id: number;
+ sha: string;
+ name: string;
+ target_url: string;
+ context: string;
+ description: string;
+ state: 'pending' | 'success' | 'failure' | 'error';
+ commit: Commit;
+ branches: Branch[];
+ created_at: string;
+ updated_at: string;
+ repository: Repository;
+ sender: User;
+}
+
+export class StatusEventModel implements StatusEventInput {
+ id: number;
+ sha: string;
+ name: string;
+ target_url: string;
+ context: string;
+ description: string;
+ state: 'error' | 'pending' | 'success' | 'failure';
+ commit: Commit;
+ branches: Branch[];
+ created_at: string;
+ updated_at: string;
+ repository: Repository;
+ sender: User;
+
+ constructor(input: StatusEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['id', 'sha', 'name', 'target_url', 'context', 'description',
+ 'state', 'commit', 'branches', 'created_at', 'updated_at', 'repository', 'sender'];
+ return isExistProperties(input, requireKeys);
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook-event-response/watch.ts b/functions/src/mappers/github/webhook-event-response/watch.ts
new file mode 100644
index 000000000..e6e90cbbe
--- /dev/null
+++ b/functions/src/mappers/github/webhook-event-response/watch.ts
@@ -0,0 +1,48 @@
+import { firestore } from 'firebase-admin';
+
+// Dashboard mappers/models
+import { GitHubEventModel, GitHubEventType } from '../event.mapper';
+import { GitHubPayloadInput, GitHubPayloadMapper } from '../payload.mapper';
+import { GitHubRepositoryMapper } from '../repository.mapper';
+import { GitHubUserMapper } from '../user.mapper';
+import { isExistProperties, HubEventActions, Repository, User } from './shared';
+
+export interface WatchEventInput {
+ action: 'started';
+ repository: Repository;
+ sender: User;
+}
+
+export class WatchEventModel implements WatchEventInput, HubEventActions {
+ action: 'started';
+ repository: Repository;
+ sender: User;
+
+ constructor(input: WatchEventInput) {
+ Object.assign(this, input);
+ }
+
+ public static isCurrentModel(input: any): boolean {
+ const requireKeys: string[] = ['action', 'repository', 'sender'];
+ return Object.keys(input).length === requireKeys.length && isExistProperties(input, requireKeys) && input.action === 'started';
+ }
+
+ convertToHubEvent(): GitHubEventModel {
+ const eventType: GitHubEventType = 'WatchEvent';
+ const payload: GitHubPayloadInput = {
+ action: this.action,
+ }
+
+ const data: GitHubEventModel = {
+ type: eventType,
+ public: true, // TODO where get
+ actor: GitHubUserMapper.import(this.sender),
+ repository: GitHubRepositoryMapper.import(this.repository, 'event'),
+ payload: GitHubPayloadMapper.import(eventType, payload),
+ createdOn: firestore.Timestamp.now(),
+ };
+
+ return data;
+ }
+
+}
diff --git a/functions/src/mappers/github/webhook.mapper.ts b/functions/src/mappers/github/webhook.mapper.ts
index a56b5263b..bc9909e70 100644
--- a/functions/src/mappers/github/webhook.mapper.ts
+++ b/functions/src/mappers/github/webhook.mapper.ts
@@ -1,3 +1,5 @@
+import { firestore } from "firebase-admin";
+
export interface GitHubRepositoryWebhookResponse {
type: string;
id: number;
@@ -10,8 +12,8 @@ export interface GitHubRepositoryWebhookResponse {
secret?: string;
insecure_ssl?: '0' | '1';
};
- updated_at: Date;
- created_at: Date;
+ updated_at: string;
+ created_at: string;
url: string;
test_url: string;
ping_url: string;
@@ -47,8 +49,8 @@ export interface GitHubRepositoryWebhookModel {
secret?: string;
insecureSsl?: '0' | '1';
};
- updatedAt: Date;
- createdAt: Date;
+ updatedOn: firestore.Timestamp;
+ createdOn: firestore.Timestamp;
url: string;
testUrl: string;
pingUrl: string;
@@ -101,8 +103,8 @@ export class GitHubRepositoryWebhookMapper {
active: input.active,
events: input.events,
config: config,
- updatedAt: input.updated_at,
- createdAt: input.created_at,
+ updatedOn: firestore.Timestamp.fromDate(new Date(input.updated_at)),
+ createdOn: firestore.Timestamp.fromDate(new Date(input.created_at)),
url: input.url,
testUrl: input.test_url,
pingUrl: input.ping_url,
diff --git a/functions/src/models/index.model.ts b/functions/src/models/index.model.ts
index dd0bbd61d..8e123b6f6 100644
--- a/functions/src/models/index.model.ts
+++ b/functions/src/models/index.model.ts
@@ -1,3 +1,4 @@
export * from './monitor.model';
export * from './ping.model';
export * from './project.model';
+export * from './repository.model';
diff --git a/functions/src/models/project.model.ts b/functions/src/models/project.model.ts
index eab1ea7f7..86bb52f41 100644
--- a/functions/src/models/project.model.ts
+++ b/functions/src/models/project.model.ts
@@ -8,6 +8,7 @@ export class ProjectModel {
monitors?: MonitorModel[] = [];
uid?: string = '';
url?: string = '';
+ repositories?: string[] = [];
constructor(uid: string = '') {
this.uid = uid;
diff --git a/functions/src/models/repository.model.ts b/functions/src/models/repository.model.ts
new file mode 100644
index 000000000..5d85d3b39
--- /dev/null
+++ b/functions/src/models/repository.model.ts
@@ -0,0 +1,45 @@
+// Dashboard hub firebase functions models/mappers
+import { DocumentData, DocumentReference, FirebaseAdmin, QuerySnapshot, WriteResult } from "../client/firebase-admin";
+
+export interface IRepository {
+ id: number;
+ fullName: string;
+ uid?: string;
+}
+
+export class RepositoryModel implements IRepository {
+ id: number;
+ fullName: string;
+ uid?: string;
+
+ public static getRepositoryReference(uid: string): DocumentReference {
+ return FirebaseAdmin.firestore().collection('repositories').doc(uid);
+ }
+
+ public static async getRepositoryById(id: number): Promise {
+ const repositoriesSnapshot: QuerySnapshot = await FirebaseAdmin.firestore().collection('repositories').where('id', '==', id).limit(1).get();
+ if (repositoriesSnapshot.empty) {
+ return null;
+ } else {
+ return repositoriesSnapshot.docs.shift().data();
+ }
+ }
+
+ public static async getRepositoryUidById(id: number): Promise {
+ const repositoriesSnapshot: QuerySnapshot = await FirebaseAdmin.firestore().collection('repositories').where('id', '==', id).limit(1).get();
+ if (repositoriesSnapshot.empty) {
+ return null;
+ } else {
+ return repositoriesSnapshot.docs.shift().id;
+ }
+ }
+
+ public static async saveRepository(repository: DocumentData): Promise {
+ return await FirebaseAdmin
+ .firestore()
+ .collection('repositories')
+ .doc(repository.uid)
+ .set(repository, { merge: true });
+ }
+
+}
\ No newline at end of file
diff --git a/functions/src/project/delete-project.ts b/functions/src/project/delete-project.ts
index 263da7869..99ddc79e5 100644
--- a/functions/src/project/delete-project.ts
+++ b/functions/src/project/delete-project.ts
@@ -3,67 +3,67 @@ import { firestore, CloudFunction, EventContext } from 'firebase-functions';
// Dashboard hub firebase functions models/mappers
import { Logger } from '../client/logger';
-import { ProjectModel } from '../models/index.model';
+import { ProjectModel, RepositoryModel } from '../models/index.model';
import { deleteMonitorPings } from '../monitor/monitor';
-import { DocumentData, DocumentSnapshot, FirebaseAdmin, WriteResult } from './../client/firebase-admin';
+import { DocumentData, DocumentReference, DocumentSnapshot, FirebaseAdmin, Transaction, WriteResult } from './../client/firebase-admin';
-export const onDeleteProjectRepositories: CloudFunction = firestore
- .document('projects/{projectUid}')
- .onDelete(async (projectSnapshot: DocumentSnapshot, context: EventContext) => {
-
- try {
- const project: DocumentData = projectSnapshot.data();
-
- if (Array.isArray(project.repositories) && project.repositories.length > 0) {
-
- const promiseList: Promise[] = [];
-
- for (const repositoryUid of project.repositories) {
- const repoData: DocumentData = (await FirebaseAdmin.firestore().collection('repositories').doc(repositoryUid).get()).data();
-
- if (Array.isArray(repoData.projects)) {
- repoData.projects = repoData.projects.filter((element: string) => element !== context.params.projectUid);
- } else {
- repoData.projects = [];
- }
-
- const repo: Promise = FirebaseAdmin.firestore().collection('repositories').doc(repositoryUid).update(repoData);
- promiseList.push(repo);
+async function deleteProjectRepositories(projectUid: string, project: ProjectModel): Promise {
+ Logger.info('deleteProjectRepositories');
+ try {
+ if (Array.isArray(project.repositories) && project.repositories.length > 0) {
+ for (const repositoryUid of project.repositories) {
+ if (!repositoryUid) {
+ continue;
}
+ const repoRef: DocumentReference = RepositoryModel.getRepositoryReference(repositoryUid);
- await Promise.all(promiseList);
- }
+ await FirebaseAdmin.firestore().runTransaction((t: Transaction) => {
+ return t.get(repoRef)
+ .then((repo: DocumentSnapshot) => {
+ const repoData: DocumentData = repo.data();
- return;
+ if (Array.isArray(repoData.projects)) {
+ repoData.projects = repoData.projects.filter((element: string) => element !== projectUid);
+ } else {
+ repoData.projects = [];
+ }
- } catch (err) {
- Logger.error(err);
- throw new Error(err);
+ t.update(repoRef, { projects: repoData.projects });
+ });
+ });
+ }
}
- });
+ } catch (err) {
+ Logger.error(err);
+ throw new Error(err);
+ }
+
+};
export const deleteProjectPings: any = async (project: ProjectModel, isExpiredFlag: boolean): Promise => {
- try {
- if (Array.isArray(project.monitors) && project.monitors.length > 0) {
- const promises: Promise[] = [];
-
- for (const monitor of project.monitors) {
- promises.push(deleteMonitorPings(project.uid, monitor.uid, isExpiredFlag))
- }
- return Promise.all(promises);
+ Logger.info('deleteProjectPings');
+ try {
+ if (Array.isArray(project.monitors) && project.monitors.length > 0) {
+ const promises: Promise[] = [];
+
+ for (const monitor of project.monitors) {
+ promises.push(deleteMonitorPings(project.uid, monitor.uid, isExpiredFlag))
}
- return Promise.all([]);
-
- } catch (err) {
- Logger.error(err);
- throw new Error(err);
+ return Promise.all(promises);
}
- };
+ return Promise.all([]);
+
+ } catch (err) {
+ Logger.error(err);
+ throw new Error(err);
+ }
+};
export const onDeleteProject: CloudFunction = firestore
.document('projects/{projectUid}')
.onDelete(async (projectSnapshot: DocumentSnapshot, context: EventContext) => {
const project: ProjectModel = projectSnapshot.data();
deleteProjectPings(project);
+ await deleteProjectRepositories(context.params.projectUid, project);
});
diff --git a/functions/src/project/update-project.ts b/functions/src/project/update-project.ts
new file mode 100644
index 000000000..797737524
--- /dev/null
+++ b/functions/src/project/update-project.ts
@@ -0,0 +1,75 @@
+// Third party modules
+import { firestore, Change, EventContext } from 'firebase-functions';
+
+// Dashboard hub firebase functions models/mappers
+import { Logger } from '../client/logger';
+import { RepositoryModel } from '../models/index.model';
+import { DocumentData, DocumentSnapshot, WriteResult } from './../client/firebase-admin';
+
+async function updateRepositories(projectUid: string, newData: DocumentData, previousData: DocumentData): Promise {
+ const isArrayNewDataRepositories: boolean = Array.isArray(newData.repositories) && newData.repositories.length > 0;
+ const isArrayPreviousDataRepositories: boolean = Array.isArray(previousData.repositories) && previousData.repositories.length > 0;
+
+ let add: string[], remove: string[];
+ const promiseList: Promise[] = [];
+
+ if (isArrayNewDataRepositories) {
+ if (isArrayPreviousDataRepositories) {
+ add = newData.repositories.filter((element: string) => previousData.repositories.findIndex((item: string) => element === item) === -1);
+ remove = previousData.repositories.filter((element: string) => newData.repositories.findIndex((item: string) => element === item) === -1);
+ } else {
+ add = newData.repositories;
+ remove = [];
+ }
+ } else {
+ add = [];
+ remove = isArrayPreviousDataRepositories ? previousData.repositories : [];
+ }
+
+ for (const item of add) {
+ const repoData: DocumentData = (await RepositoryModel.getRepositoryReference(item).get()).data();
+
+ if (Array.isArray(repoData.projects)) {
+ const foundIndex: number = repoData.projects.findIndex((element: string) => element === projectUid);
+ if (foundIndex === -1) {
+ repoData.projects.push(projectUid);
+ }
+ } else {
+ repoData.projects = [projectUid];
+ }
+
+ const repo: Promise = RepositoryModel.getRepositoryReference(repoData.uid).update({projects: repoData.projects});
+ promiseList.push(repo);
+ }
+
+ for (const item of remove) {
+ const repoData: DocumentData = (await RepositoryModel.getRepositoryReference(item).get()).data();
+
+ if (Array.isArray(repoData.projects)) {
+ repoData.projects = repoData.projects.filter((element: string) => element !== projectUid);
+ } else {
+ repoData.projects = [];
+ }
+
+ const repo: Promise = RepositoryModel.getRepositoryReference(repoData.uid).update({projects: repoData.projects});
+ promiseList.push(repo);
+ }
+
+ return Promise.all(promiseList);
+}
+
+export const onUpdateProject: any = firestore
+ .document('projects/{projectUid}')
+ .onUpdate(async (change: Change, context: EventContext) => {
+
+ try {
+ const newData: DocumentData = change.after.data();
+ const previousData: DocumentData = change.before.data();
+
+ return updateRepositories(context.params.projectUid, newData, previousData);
+ } catch (err) {
+ Logger.error(err);
+ throw new Error(err);
+ }
+
+ });
diff --git a/functions/src/project/update-repositories.ts b/functions/src/project/update-repositories.ts
deleted file mode 100644
index 877ab4365..000000000
--- a/functions/src/project/update-repositories.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-// Third party modules
-import { firestore, Change, EventContext } from 'firebase-functions';
-
-// Dashboard hub firebase functions models/mappers
-import { Logger } from '../client/logger';
-import { DocumentData, DocumentSnapshot, FirebaseAdmin, WriteResult } from './../client/firebase-admin';
-
-export const onUpdateProjectRepositories: any = firestore
- .document('projects/{projectUid}')
- .onUpdate(async (change: Change, context: EventContext) => {
-
- try {
- const newData: DocumentData = change.after.data();
- const previousData: DocumentData = change.before.data();
- const isArrayNewDataRepositories: boolean = Array.isArray(newData.repositories) && newData.repositories.length > 0;
- const isArrayPreviousDataRepositories: boolean = Array.isArray(previousData.repositories) && previousData.repositories.length > 0;
-
- let add: string[], remove: string[];
- const promiseList: Promise[] = [];
-
- if (isArrayNewDataRepositories) {
- if (isArrayPreviousDataRepositories) {
- add = newData.repositories.filter((element: string) => previousData.repositories.findIndex((item: string) => element === item) === -1);
- remove = previousData.repositories.filter((element: string) => newData.repositories.findIndex((item: string) => element === item) === -1);
- } else {
- add = newData.repositories;
- remove = [];
- }
- } else {
- add = [];
- remove = isArrayPreviousDataRepositories ? previousData.repositories : [];
- }
-
- for (const repositoryUid of add) {
- const repoData: DocumentData = (await FirebaseAdmin.firestore().collection('repositories').doc(repositoryUid).get()).data();
-
- if (Array.isArray(repoData.projects)) {
- const foundIndex: number = repoData.projects.findIndex((element: string) => element === context.params.projectUid);
- if (foundIndex === -1) {
- repoData.projects.push(context.params.projectUid);
- }
- } else {
- repoData.projects = [context.params.projectUid];
- }
-
- const repo: Promise = FirebaseAdmin.firestore().collection('repositories').doc(repositoryUid).update(repoData);
- promiseList.push(repo);
- }
-
- for (const repositoryUid of remove) {
- const repoData: DocumentData = (await FirebaseAdmin.firestore().collection('repositories').doc(repositoryUid).get()).data();
-
- if (Array.isArray(repoData.projects)) {
- repoData.projects = repoData.projects.filter((element: string) => element !== context.params.projectUid);
- } else {
- repoData.projects = [];
- }
-
- const repo: Promise = FirebaseAdmin.firestore().collection('repositories').doc(repositoryUid).set(repoData, { merge: true });
- promiseList.push(repo);
- }
-
- return Promise.all(promiseList);
-
- } catch (err) {
- Logger.error(err);
- throw new Error(err);
- }
-
- });
diff --git a/functions/src/repository/create-git-webhook-repository.ts b/functions/src/repository/create-git-webhook-repository.ts
index 4687d563f..9b90fcadc 100644
--- a/functions/src/repository/create-git-webhook-repository.ts
+++ b/functions/src/repository/create-git-webhook-repository.ts
@@ -1,8 +1,10 @@
import { enviroment } from '../environments/environment';
import { GitHubRepositoryWebhookMapper, GitHubRepositoryWebhookModel, GitHubRepositoryWebhookRequestCreate, GitHubRepositoryWebhookResponse } from '../mappers/github/webhook.mapper';
-import { DocumentData, DocumentReference, FirebaseAdmin } from './../client/firebase-admin';
+import { RepositoryModel } from '../models/index.model';
+import { DocumentData, DocumentReference } from './../client/firebase-admin';
import { GitHubClientPost } from './../client/github';
import { Logger } from './../client/logger';
+import { deleteWebhook } from './delete-git-webhook-repository';
import { findWebhook } from './find-git-webhook-repository';
export interface CreateGitWebhookRepositoryInput {
@@ -12,7 +14,7 @@ export interface CreateGitWebhookRepositoryInput {
export const onCreateGitWebhookRepository: any = async (token: string, repositoryUid: string) => {
try {
- const repositorySnapshot: DocumentReference = FirebaseAdmin.firestore().collection('repositories').doc(repositoryUid);
+ const repositorySnapshot: DocumentReference = RepositoryModel.getRepositoryReference(repositoryUid);
const repository: DocumentData = (await repositorySnapshot.get()).data();
const webhook: GitHubRepositoryWebhookModel = await getWebhook(repository.fullName, token);
@@ -20,7 +22,7 @@ export const onCreateGitWebhookRepository: any = async (token: string, repositor
repository.webhook = webhook;
await repositorySnapshot.update(repository);
- Logger.info({ webhook });
+ Logger.info(webhook ? 'Webhook created' : 'Webhook empty');
return repository;
} catch (error) {
@@ -30,7 +32,7 @@ export const onCreateGitWebhookRepository: any = async (token: string, repositor
};
-export function createWebhook(repositoryUid: string, token: string): Promise {
+export function createWebhook(repositoryFullName: string, token: string): Promise {
const body: GitHubRepositoryWebhookRequestCreate = {
// name: enviroment.githubWebhook.name,
name: 'web',
@@ -38,20 +40,43 @@ export function createWebhook(repositoryUid: string, token: string): Promise(`/repos/${repositoryUid}/hooks`, token, body);
+ return GitHubClientPost(`/repos/${repositoryFullName}/hooks`, token, body);
}
-export async function getWebhook(repositoryUid: string, token: string): Promise {
+export async function getWebhook(repositoryFullName: string, token: string): Promise {
- const exist: GitHubRepositoryWebhookResponse = await findWebhook(repositoryUid, token);
+ const exist: GitHubRepositoryWebhookResponse = await findWebhook(repositoryFullName, token);
if (exist) {
- return GitHubRepositoryWebhookMapper.import(exist);
+ let isEqual: boolean = exist.events.length === enviroment.githubWebhook.events.length
+ && exist.config.content_type === enviroment.githubWebhook.content_type
+ && exist.config.insecure_ssl === enviroment.githubWebhook.insecure_ssl
+ && ((!exist.config.secret && !enviroment.githubWebhook.secret) || (!!exist.config.secret && !!enviroment.githubWebhook.secret));
+
+ if (isEqual) {
+
+ for (const existItem of exist.events) {
+ if (enviroment.githubWebhook.events.findIndex((envItem: string) => existItem === envItem) === -1) {
+ isEqual = false;
+ break;
+ }
+ }
+
+ if (isEqual) {
+ Logger.info('Webhook is exist');
+ return GitHubRepositoryWebhookMapper.import(exist);
+ }
+ }
+ Logger.info('Webhook is deleting');
+ await deleteWebhook(repositoryFullName, exist.id, token);
}
- return GitHubRepositoryWebhookMapper.import(await createWebhook(repositoryUid, token));
+
+ Logger.info('Webhook is creating');
+ return GitHubRepositoryWebhookMapper.import(await createWebhook(repositoryFullName, token));
}
diff --git a/functions/src/repository/create-repository.ts b/functions/src/repository/create-repository.ts
index 147da16c4..353a367bc 100644
--- a/functions/src/repository/create-repository.ts
+++ b/functions/src/repository/create-repository.ts
@@ -3,11 +3,11 @@ import { firestore, CloudFunction, EventContext } from 'firebase-functions';
// Dashboard hub firebase functions models/mappers
import { Logger } from '../client/logger';
-import { DocumentData, DocumentSnapshot, FirebaseAdmin, QueryDocumentSnapshot } from './../client/firebase-admin';
+import { GitHubRepositoryModel } from '../mappers/github/index.mapper';
+import { DocumentData, DocumentReference, DocumentSnapshot, FieldPath, FirebaseAdmin, Query, QueryDocumentSnapshot, QuerySnapshot, Transaction } from './../client/firebase-admin';
async function addProjects(repositoryUid: string, repo: DocumentData): Promise {
const projects: QueryDocumentSnapshot[] = (await FirebaseAdmin.firestore().collection('projects').where('repositories', 'array-contains', repositoryUid).get()).docs;
-
if (!Array.isArray(repo.projects)) {
repo.projects = [];
}
@@ -16,23 +16,82 @@ async function addProjects(repositoryUid: string, repo: DocumentData): Promise 0) {
+ if (repo.projects.length > 0) {
return true;
}
return false;
}
+async function removeDublicateRepository(repositoryUid: string, repo: DocumentData, repoRef: DocumentReference): Promise {
+ const repoQuerySnap: QuerySnapshot = (await FirebaseAdmin.firestore().collection('repositories').where('id', '==', repo.id).where('uid', '<', repositoryUid).where('uid', '>', repositoryUid).get());
+ if (repoQuerySnap.empty) {
+ return true;
+ } else {
+
+ const existRepoUid: string = repoQuerySnap.docs.shift().id;
+
+ // update repo data in user
+ const usersRef: Query = FirebaseAdmin.firestore().collection('users').where(new FieldPath('repositories', 'uids'), 'array-contains', repositoryUid);
+ await FirebaseAdmin.firestore().runTransaction((t: Transaction) => {
+ return t.get(usersRef)
+ .then((snap: QuerySnapshot) => {
+ snap.forEach((element: QueryDocumentSnapshot) => {
+ const userData: DocumentData = element.data();
+ if (userData.repositories && Array.isArray(userData.repositories.data) && userData.repositories.data.length > 0) {
+ const repos: GitHubRepositoryModel[] = userData.repositories.data;
+ const uids: string[] = userData.repositories.uids;
+ const foundIndexObj: number = repos.findIndex((item: GitHubRepositoryModel) => item.uid && item.uid === repositoryUid);
+ const foundIndexUids: number = uids.findIndex((uid: string) => uid === repositoryUid);
+
+ if (foundIndexObj > -1 && foundIndexUids > -1) {
+ repos[foundIndexObj].uid = existRepoUid;
+ uids[foundIndexUids] = existRepoUid;
+ t.update(element.ref, { repositories: { ...userData.repositories, data: repos, uids } });
+ }
+ }
+ });
+
+ });
+ });
+
+ // update repo data in project
+ const projectsRef: Query = FirebaseAdmin.firestore().collection('projects').where('repositories', 'array-contains', repositoryUid);
+ await FirebaseAdmin.firestore().runTransaction((t: Transaction) => {
+ return t.get(projectsRef)
+ .then((snap: QuerySnapshot) => {
+ snap.forEach((element: QueryDocumentSnapshot) => {
+ const projectData: DocumentData = element.data();
+ if (Array.isArray(projectData.repositories) && projectData.repositories.length > 0) {
+ const uids: string[] = projectData.repositories;
+ const foundIndex: number = uids.findIndex((uid: string) => uid === repositoryUid);
+
+ if (foundIndex > -1) {
+ uids[foundIndex] = existRepoUid;
+ t.update(element.ref, { repositories: uids });
+ }
+ }
+ });
+
+ });
+ });
+
+ await repoRef.delete();
+ return false;
+ }
+}
+
export const onCreateRepository: CloudFunction = firestore
.document('repositories/{repositoryUid}')
.onCreate(async (repositorySnapshot: DocumentSnapshot, context: EventContext) => {
try {
const newData: DocumentData = repositorySnapshot.data();
- const isNeedUpdate: boolean = await addProjects(context.params.repositoryUid, newData);
+ let isNeedUpdate: boolean = await removeDublicateRepository(context.params.repositoryUid, newData, repositorySnapshot.ref);
+ isNeedUpdate = isNeedUpdate && await addProjects(context.params.repositoryUid, newData);
+
if (isNeedUpdate) {
- return repositorySnapshot.ref.update(newData);
+ await repositorySnapshot.ref.update(newData);
}
- return newData;
} catch (err) {
Logger.error(err);
throw new Error(err);
diff --git a/functions/src/repository/delete-git-webhook-repository.ts b/functions/src/repository/delete-git-webhook-repository.ts
index a7e86029a..3bc0ecc58 100644
--- a/functions/src/repository/delete-git-webhook-repository.ts
+++ b/functions/src/repository/delete-git-webhook-repository.ts
@@ -1,21 +1,35 @@
-import { DocumentData, DocumentReference, FirebaseAdmin } from './../client/firebase-admin';
+import { RepositoryModel } from '../models/index.model';
+import { DocumentData, DocumentReference } from './../client/firebase-admin';
import { GitHubClientDelete } from './../client/github';
import { Logger } from './../client/logger';
export interface DeleteGitWebhookRepositoryInput {
token: string;
- repositoryUid: string;
+ data: { uid?: string, id?: number };
}
-export const onDeleteGitWebhookRepository: any = async (token: string, repositoryUid: string) => {
- const repositorySnapshot: DocumentReference = FirebaseAdmin.firestore().collection('repositories').doc(repositoryUid);
- const repository: DocumentData = (await repositorySnapshot.get()).data();
+export const onDeleteGitWebhookRepository: any = async (token: string, data: { uid?: string, id?: number }) => {
+ let repository: DocumentData;
+ let repositoryRef: DocumentReference;
+
+ if (!(data && (data.uid || data.id))) {
+ Logger.error('Invalid input data!');
+ return null;
+ }
+
+ if (data.uid) {
+ repositoryRef = RepositoryModel.getRepositoryReference(data.uid);
+ repository = (await repositoryRef.get()).data();
+ } else if (data.id) {
+ repository = await RepositoryModel.getRepositoryById(data.id);
+ repositoryRef = RepositoryModel.getRepositoryReference(repository.uid);
+ }
try {
- if (repository.projects && repository.projects.length === 1 && repository.webhook) {
- await deleteWebhook(repository, token);
+ if (repository && repository.projects && repository.projects.length === 1 && repository.webhook) {
+ await deleteWebhook(repository.fullName, repository.webhook.id, token);
repository.webhook = null;
- await repositorySnapshot.update(repository);
+ await repositoryRef.update(repository);
}
Logger.info(`Webhook removed for ${repository.fullName}`);
@@ -29,6 +43,6 @@ export const onDeleteGitWebhookRepository: any = async (token: string, repositor
};
-function deleteWebhook(repository: DocumentData, token: string): Promise {
- return GitHubClientDelete(`/repos/${repository.fullName}/hooks/${repository.webhook.id}`, token);
+export function deleteWebhook(repositoryFullName: string, webhookId: number, token: string): Promise {
+ return GitHubClientDelete(`/repos/${repositoryFullName}/hooks/${webhookId}`, token);
}
diff --git a/functions/src/repository/find-git-webhook-repository.ts b/functions/src/repository/find-git-webhook-repository.ts
index cc788b924..a06c4c0d8 100644
--- a/functions/src/repository/find-git-webhook-repository.ts
+++ b/functions/src/repository/find-git-webhook-repository.ts
@@ -3,13 +3,13 @@ import { GitHubRepositoryWebhookResponse } from '../mappers/github/webhook.mappe
import { GitHubClient } from './../client/github';
-export function listWebhook(repositoryUid: string, token: string): Promise {
- return GitHubClient(`/repos/${repositoryUid}/hooks`, token);
+export function listWebhook(repositoryFullName: string, token: string): Promise {
+ return GitHubClient(`/repos/${repositoryFullName}/hooks`, token);
}
-export async function findWebhook(repositoryUid: string, token: string): Promise {
- const list: GitHubRepositoryWebhookResponse[] = await listWebhook(repositoryUid, token);
+export async function findWebhook(repositoryFullName: string, token: string): Promise {
+ const list: GitHubRepositoryWebhookResponse[] = await listWebhook(repositoryFullName, token);
return list.find((elem: GitHubRepositoryWebhookResponse) => elem && elem.config && elem.config.url === enviroment.githubWebhook.url)
}
diff --git a/functions/src/repository/info.ts b/functions/src/repository/info.ts
index 94840f5a5..c70e63938 100644
--- a/functions/src/repository/info.ts
+++ b/functions/src/repository/info.ts
@@ -1,6 +1,7 @@
// Third party modules
import { firestore } from 'firebase-admin';
+// Dashboard hub firebase functions models/mappers
import {
GitHubContributorInput, GitHubContributorMapper,
GitHubEventInput, GitHubEventMapper,
@@ -19,10 +20,14 @@ import { getWebhook } from './create-git-webhook-repository';
export interface RepositoryInfoInput {
token: string;
- fullName: string;
+ repository: {
+ uid: string;
+ id: number;
+ fullName: string;
+ }
}
-export const getRepositoryInfo: any = async (token: string, fullName: string) => {
+export const getRepositoryInfo: any = async (token: string, repository: { uid: string; id: number; fullName: string; }) => {
let data: [
GitHubRepositoryInput,
GitHubPullRequestInput[],
@@ -37,37 +42,37 @@ export const getRepositoryInfo: any = async (token: string, fullName: string) =>
try {
data = await Promise.all([
- GitHubClient(`/repos/${fullName}`, token),
- GitHubClient(`/repos/${fullName}/pulls?state=open`, token),
- GitHubClient(`/repos/${fullName}/events`, token),
- GitHubClient(`/repos/${fullName}/releases`, token),
- GitHubClient(`/repos/${fullName}/issues`, token),
- GitHubClient(`/repos/${fullName}/stats/contributors`, token),
- GitHubClient(`/repos/${fullName}/milestones`, token),
+ GitHubClient(`/repos/${repository.fullName}`, token),
+ GitHubClient(`/repos/${repository.fullName}/pulls?state=open`, token),
+ GitHubClient(`/repos/${repository.fullName}/events`, token),
+ GitHubClient(`/repos/${repository.fullName}/releases`, token),
+ GitHubClient(`/repos/${repository.fullName}/issues`, token),
+ GitHubClient(`/repos/${repository.fullName}/stats/contributors`, token),
+ GitHubClient(`/repos/${repository.fullName}/milestones`, token),
]);
mappedData = {
...GitHubRepositoryMapper.import(data[0], 'all'),
pullRequests: data[1] ? data[1].map((pullrequest: GitHubPullRequestInput) => GitHubPullRequestMapper.import(pullrequest)) : [],
events: data[2] ? data[2].map((event: GitHubEventInput) => GitHubEventMapper.import(event)) : [],
- releases: data[3] ? data[3].map((release: GitHubReleaseInput) => GitHubReleaseMapper.import(release)) : [],
+ releases: data[3] ? GitHubReleaseMapper.sortReleaseList(data[3].map((release: GitHubReleaseInput) => GitHubReleaseMapper.import(release))) : [],
issues: Array.isArray(data[4]) ? data[4].map((issue: GitHubIssueInput) => GitHubIssueMapper.import(issue)) : [],
contributors: Array.isArray(data[5]) ? data[5].map((contributor: GitHubContributorInput) => GitHubContributorMapper.import(contributor)) : [],
milestones: Array.isArray(data[6]) ? data[6].map((milestone: GitHubMilestoneInput) => GitHubMilestoneMapper.import(milestone)) : [],
updatedAt: firestore.Timestamp.fromDate(new Date()),
};
} catch (error) {
- Logger.error(error, [`Repository Path: ${fullName}`]);
+ Logger.error(error, [`Repository Path: ${repository.fullName}`]);
throw new Error(error);
}
try {
- mappedData.webhook = await getWebhook(fullName, token);
+ mappedData.webhook = await getWebhook(repository.fullName, token);
} catch (error) {
- Logger.error(error, ['Webhook failed', `Repository Path: ${fullName}`]);
+ Logger.error(error, ['Webhook failed', `Repository Path: ${repository.fullName}`]);
}
Logger.info({
- repository: fullName,
+ repository: repository.fullName,
imported: {
pullRequests: mappedData && mappedData.pullRequests.length || 0,
events: mappedData && mappedData.events.length || 0,
@@ -79,10 +84,12 @@ export const getRepositoryInfo: any = async (token: string, fullName: string) =>
},
});
+ mappedData.uid = repository.uid;
+
await FirebaseAdmin
.firestore()
.collection('repositories')
- .doc(GitHubRepositoryMapper.fullNameToUid(fullName))
+ .doc(mappedData.uid)
.set(mappedData, { merge: true });
return mappedData;
diff --git a/functions/src/repository/response-git-webhook-repository.ts b/functions/src/repository/response-git-webhook-repository.ts
index 9eb32fdba..de3f466a8 100644
--- a/functions/src/repository/response-git-webhook-repository.ts
+++ b/functions/src/repository/response-git-webhook-repository.ts
@@ -1,8 +1,30 @@
// Third party modules
import * as CORS from 'cors';
+import * as crypto from "crypto";
import { https, HttpsFunction, Response } from 'firebase-functions';
+import { enviroment } from '../environments/environment';
+
+// Dashboard hub firebase functions models/mappers
+import { GitHubClient } from '../client/github';
import { Logger } from '../client/logger';
+import { GitHubContributorInput, GitHubContributorMapper } from '../mappers/github/index.mapper';
+import {
+ CreateEventModel,
+ IssuesEventModel,
+ IssueCommentEventModel,
+ MemberEventModel,
+ MilestoneEventModel,
+ PullRequestEventModel,
+ PushEventModel,
+ ReleaseEventModel,
+ RepositoryEventModel,
+ StatusEventModel,
+ WatchEventModel,
+} from '../mappers/github/webhook-event-response';
+import { addHubEventToCollection, HubEventActions } from '../mappers/github/webhook-event-response/shared';
+import { RepositoryModel } from '../models/index.model';
+import { DocumentData, FieldPath, FirebaseAdmin, QuerySnapshot } from './../client/firebase-admin';
// tslint:disable-next-line: typedef
const cors = CORS({
@@ -16,7 +38,205 @@ export interface ResponseGitWebhookRepositoryInput {
export const onResponseGitWebhookRepository: HttpsFunction = https.onRequest((req: https.Request, res: Response) => {
return cors(req, res, () => {
- Logger.info(`${req.protocol}://${req.hostname} ; onResponseGitWebhookRepository: success!`);
- res.status(200).send();
+ const sig: string = 'sha1=' + crypto.createHmac('sha1', enviroment.githubWebhook.secret).update(req.rawBody).digest('hex');
+
+ if (sig !== req.headers['x-hub-signature']) {
+ res.status(401).send('Error secret token!');
+ return;
+ }
+
+ const inputData: any = req.body;
+ let result: Promise;
+
+ Logger.info(Object.keys(inputData));
+ Logger.info(inputData);
+
+ if (IssueCommentEventModel.isCurrentModel(inputData)) {
+
+ result = issueCommentEvent(new IssueCommentEventModel(inputData));
+
+ } else if (IssuesEventModel.isCurrentModel(inputData)) {
+
+ result = issuesEvent(new IssuesEventModel(inputData));
+
+ } else if (CreateEventModel.isCurrentModel(inputData)) {
+
+ result = createEvent(new CreateEventModel(inputData));
+
+ } else if (PushEventModel.isCurrentModel(inputData)) {
+
+ result = pushEvent(new PushEventModel(inputData));
+
+ } else if (PullRequestEventModel.isCurrentModel(inputData)) {
+
+ result = pullRequestEvent(new PullRequestEventModel(inputData));
+
+ } else if (ReleaseEventModel.isCurrentModel(inputData)) {
+
+ result = releaseEvent(new ReleaseEventModel(inputData));
+
+ } else if (MilestoneEventModel.isCurrentModel(inputData)) {
+
+ result = milestoneEvent(new MilestoneEventModel(inputData));
+
+ } else if (WatchEventModel.isCurrentModel(inputData)) {
+
+ result = watchEvent(new WatchEventModel(inputData));
+
+ } else if (RepositoryEventModel.isCurrentModel(inputData)) {
+
+ result = repositoryEvent(new RepositoryEventModel(inputData));
+
+ } else if (MemberEventModel.isCurrentModel(inputData)) {
+
+ result = memberEvent(new MemberEventModel(inputData));
+
+ } else if (StatusEventModel.isCurrentModel(inputData)) {
+
+ result = statusEvent(new StatusEventModel(inputData));
+
+ } else {
+ Logger.error('Not found parser for event');
+ }
+
+ if (result) {
+ result
+ .then(() => {
+ Logger.info('Parsing done!');
+ res.status(200).send();
+ })
+ .catch((err: any) => {
+ Logger.error('Parser error!');
+ Logger.error(err);
+ res.status(500).send('Parser error!');
+ });
+ } else {
+ // res.status(200).send();
+ res.status(200).send('Not found parser for event');
+ }
+
});
});
+
+async function simpleHubEvent(data: HubEventActions): Promise {
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(data.repository.id);
+
+ addHubEventToCollection(repository, data);
+ await RepositoryModel.saveRepository(repository);
+}
+
+async function issuesEvent(data: IssuesEventModel): Promise {
+ Logger.info('issuesEvent');
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(data.repository.id);
+
+ data.updateData(repository);
+
+ addHubEventToCollection(repository, data);
+ await RepositoryModel.saveRepository(repository);
+}
+
+async function repositoryEvent(data: RepositoryEventModel): Promise {
+ Logger.info('repositoryEvent');
+ await data.updateData();
+}
+
+async function pullRequestEvent(data: PullRequestEventModel): Promise {
+ Logger.info('pullRequestEvent');
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(data.repository.id);
+
+ data.updateData(repository);
+
+ addHubEventToCollection(repository, data);
+ await RepositoryModel.saveRepository(repository);
+}
+
+async function releaseEvent(data: ReleaseEventModel): Promise {
+ Logger.info('releaseEvent');
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(data.repository.id);
+
+ data.updateData(repository);
+
+ addHubEventToCollection(repository, data);
+ await RepositoryModel.saveRepository(repository);
+}
+
+async function milestoneEvent(data: MilestoneEventModel): Promise {
+ Logger.info('milestoneEvent');
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(data.repository.id);
+
+ data.updateData(repository);
+
+ await RepositoryModel.saveRepository(repository);
+}
+
+async function watchEvent(data: WatchEventModel): Promise {
+ Logger.info('watchEvent');
+ await simpleHubEvent(data);
+}
+
+async function pushEvent(data: PushEventModel): Promise {
+ Logger.info('pushEvent');
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(data.repository.id);
+
+ addHubEventToCollection(repository, data);
+ await updateContributors(repository);
+
+ await RepositoryModel.saveRepository(repository);
+}
+
+async function issueCommentEvent(data: IssueCommentEventModel): Promise {
+ Logger.info('issueCommentEvent');
+ await simpleHubEvent(data);
+}
+
+async function createEvent(data: CreateEventModel): Promise {
+ Logger.info('createEvent');
+ await simpleHubEvent(data);
+}
+
+async function memberEvent(data: MemberEventModel): Promise {
+ Logger.info('memberEvent');
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(data.repository.id);
+
+ await updateContributors(repository);
+
+ await RepositoryModel.saveRepository(repository);
+}
+
+async function statusEvent(data: StatusEventModel): Promise {
+ Logger.info('statusEvent');
+ const repository: DocumentData = await RepositoryModel.getRepositoryById(data.repository.id);
+
+ await updateContributors(repository);
+
+ await RepositoryModel.saveRepository(repository);
+}
+
+async function updateContributors(repository: DocumentData): Promise {
+ const usersRef: QuerySnapshot = await (FirebaseAdmin.firestore().collection('users').where(new FieldPath('repositories', 'uids'), 'array-contains', repository.uid).get());
+
+ if (!usersRef.empty) {
+ for (const element of usersRef.docs) {
+ const userData: DocumentData = element.data();
+ const githubToken: string = userData && userData.oauth ? userData.oauth.githubToken : null;
+ if (githubToken) {
+ try {
+ // TODO return empty object
+ // const delay: any = (ms: number) => new Promise((_: any) => setTimeout(_, ms));
+ // await delay(30000);
+ const response: GitHubContributorInput[] = await GitHubClient(`/repos/${repository.fullName}/stats/contributors`, githubToken);
+ if (Array.isArray(response) && response.length > 0) {
+ repository.contributors = response.map((contributor: GitHubContributorInput) => GitHubContributorMapper.import(contributor));
+ Logger.info('Repository contributors updated');
+ break;
+ } else {
+ Logger.info('Repository contributors empty');
+ }
+ } catch (err) {
+ Logger.error(err);
+ }
+ }
+ }
+ }
+
+}
diff --git a/functions/src/repository/update-repository.ts b/functions/src/repository/update-repository.ts
index d3e4ce069..5b4ca81e2 100644
--- a/functions/src/repository/update-repository.ts
+++ b/functions/src/repository/update-repository.ts
@@ -3,7 +3,8 @@ import { firestore, Change, CloudFunction, EventContext } from 'firebase-functio
// Dashboard hub firebase functions models/mappers
import { Logger } from '../client/logger';
-import { DocumentData, DocumentSnapshot, FirebaseAdmin } from './../client/firebase-admin';
+import { RepositoryModel } from '../models/index.model';
+import { DocumentData, DocumentSnapshot } from './../client/firebase-admin';
export const onUpdateRepository: CloudFunction> = firestore
.document('repositories/{repositoryUid}')
@@ -13,7 +14,8 @@ export const onUpdateRepository: CloudFunction> = fires
const newData: DocumentData = change.after.data();
if (!newData.projects || Array.isArray(newData.projects) && newData.projects.length === 0) {
- return FirebaseAdmin.firestore().collection('repositories').doc(context.params.repositoryUid).delete();
+ Logger.info(`Delete repository ${context.params.repositoryUid}`);
+ return RepositoryModel.getRepositoryReference(context.params.repositoryUid).delete();
}
return newData;
diff --git a/functions/src/user/create-user.ts b/functions/src/user/create-user.ts
new file mode 100644
index 000000000..ba11928b3
--- /dev/null
+++ b/functions/src/user/create-user.ts
@@ -0,0 +1,48 @@
+// Third party modules
+import { firestore, CloudFunction, EventContext } from 'firebase-functions';
+import { v4 as uuid } from 'uuid';
+
+// Dashboard hub firebase functions models/mappers
+import { Logger } from '../client/logger';
+import { GitHubRepositoryModel } from '../mappers/github/index.mapper';
+import { DocumentData, DocumentSnapshot } from './../client/firebase-admin';
+
+// Dashboard models
+import { RepositoryModel } from '../models/index.model';
+
+async function addUidToRepositories(user: DocumentData): Promise {
+ if (user.repositories && Array.isArray(user.repositories.data) && user.repositories.data.length > 0) {
+ const uids: string[] = [];
+ user.repositories.data = user.repositories.data
+ .filter((item: GitHubRepositoryModel) => item && item.id !== null && item.id !== undefined);
+
+ for (const item of user.repositories.data) {
+ const exitsRepoUid: string = await RepositoryModel.getRepositoryUidById(item.id);
+ if (exitsRepoUid) {
+ item.uid = exitsRepoUid;
+ } else {
+ item.uid = uuid();
+ }
+ uids.push(item.uid);
+ }
+ user.repositories.data.uids = uids
+ return true;
+ }
+
+ return false;
+}
+
+export const onCreateUser: CloudFunction = firestore
+ .document('users/{userUid}')
+ .onCreate(async (userSnapshot: DocumentSnapshot, context: EventContext) => {
+ try {
+ const newData: DocumentData = userSnapshot.data();
+ const isNeedUpdate: boolean = await addUidToRepositories(newData);
+ if (isNeedUpdate) {
+ await userSnapshot.ref.update(newData);
+ }
+ } catch (err) {
+ Logger.error(err);
+ throw new Error(err);
+ }
+ });
diff --git a/functions/src/user/events.ts b/functions/src/user/events.ts
index 8195426f1..d79a8251e 100644
--- a/functions/src/user/events.ts
+++ b/functions/src/user/events.ts
@@ -1,4 +1,4 @@
-import { FirebaseAdmin } from './../client/firebase-admin';
+import { DocumentReference, FirebaseAdmin, WriteBatch } from './../client/firebase-admin';
import { GitHubEventInput, GitHubEventMapper, GitHubEventModel } from '../mappers/github/index.mapper';
import { GitHubClient } from './../client/github';
@@ -10,6 +10,8 @@ export interface EventsInput {
}
export const getUserEvents: any = async (token: string, uid: string, username: string) => {
+ const batch: WriteBatch = FirebaseAdmin.firestore().batch();
+ const userRef: DocumentReference = FirebaseAdmin.firestore().collection('users').doc(uid);
let events: GitHubEventInput[] = [];
try {
events = await GitHubClient(`/users/${username}/events`, token);
@@ -19,13 +21,12 @@ export const getUserEvents: any = async (token: string, uid: string, username: s
}
const mappedEvents: GitHubEventModel[] = events.map((event: GitHubEventInput) => GitHubEventMapper.import(event));
- await FirebaseAdmin
- .firestore()
- .collection('users')
- .doc(uid)
- .set({
+ await batch
+ .update(userRef, {
activity: mappedEvents,
- }, { merge: true });
+ });
+
+ await batch.commit();
return mappedEvents;
};
diff --git a/functions/src/user/repos.ts b/functions/src/user/repos.ts
index 43d629251..3a32898d1 100644
--- a/functions/src/user/repos.ts
+++ b/functions/src/user/repos.ts
@@ -2,7 +2,7 @@
import * as firebase from 'firebase-admin';
// Dashboard hub firebase functions mappers
-import { FirebaseAdmin } from './../client/firebase-admin';
+import { DocumentReference, FirebaseAdmin, WriteBatch } from './../client/firebase-admin';
import { GitHubClient } from './../client/github';
import { Logger } from './../client/logger';
import { GitHubRepositoryInput, GitHubRepositoryMapper, GitHubRepositoryModel } from './../mappers/github/repository.mapper';
@@ -12,6 +12,8 @@ export interface ReposInput {
}
export const getUserRepos: any = async (token: string, uid: string) => {
+ const batch: WriteBatch = FirebaseAdmin.firestore().batch();
+ const userRef: DocumentReference = FirebaseAdmin.firestore().collection('users').doc(uid);
let repositories: GitHubRepositoryInput[] = [];
try {
repositories = await GitHubClient('/user/repos?visibility=public&affiliation=owner', token);
@@ -29,16 +31,15 @@ export const getUserRepos: any = async (token: string, uid: string) => {
},
});
- await FirebaseAdmin
- .firestore()
- .collection('users')
- .doc(uid)
- .set({
+ await batch
+ .update(userRef, {
repositories: {
lastUpdated: firebase.firestore.Timestamp.fromDate(new Date()),
data: mappedRepos,
},
- }, { merge: true });
+ });
+
+ await batch.commit();
return mappedRepos;
};
diff --git a/functions/src/user/stats.ts b/functions/src/user/stats.ts
index 6b134f542..73211b82c 100644
--- a/functions/src/user/stats.ts
+++ b/functions/src/user/stats.ts
@@ -12,13 +12,18 @@ export const onUpdateUserStats: any = firestore
.onWrite((change: Change, context: EventContext) => {
const user: DocumentData = change.after.data();
+ if (!user) {
+ // TODO delete
+ return null;
+ }
+
const data: GitHubUserStatsModel = {
name: user.name,
username: user.username,
avatarUrl: user.avatarUrl,
github: {
repository: {
- total: user.repositories.data.length || 0,
+ total: user.repositories && user.repositories.data ? user.repositories.data.length : 0,
},
activity: {
latest: user.activity ? user.activity[0] : {},
diff --git a/functions/src/user/update-user.ts b/functions/src/user/update-user.ts
new file mode 100644
index 000000000..b9be161ea
--- /dev/null
+++ b/functions/src/user/update-user.ts
@@ -0,0 +1,96 @@
+// Third party modules
+import { firestore, Change, EventContext } from 'firebase-functions';
+import { v4 as uuid } from 'uuid';
+
+// Dashboard hub firebase functions models/mappers
+import { Logger } from '../client/logger';
+import { GitHubRepositoryModel } from '../mappers/github/index.mapper';
+import { DocumentData, DocumentSnapshot } from './../client/firebase-admin';
+
+// Dashboard models
+import { RepositoryModel } from '../models/index.model';
+
+async function updateRepositories(newData: DocumentData, previousData: DocumentData): Promise {
+ const isArrayNewDataRepositories: boolean = !!(newData && newData.repositories && Array.isArray(newData.repositories.data) && newData.repositories.data.length > 0);
+
+ if (!isArrayNewDataRepositories) {
+ return null;
+ }
+
+ const newRepos: GitHubRepositoryModel[] = newData.repositories.data;
+ const isArrayPreviousDataRepositories: boolean = !!(previousData && previousData.repositories && Array.isArray(previousData.repositories.data) && previousData.repositories.data.length > 0);
+ const oldRepos: GitHubRepositoryModel[] = isArrayPreviousDataRepositories ? previousData.repositories.data : [];
+ let isExitFn: boolean = true;
+
+ if (isArrayPreviousDataRepositories && newRepos.length === oldRepos.length) {
+ for (const item1 of newRepos) {
+ if (
+ (!item1)
+ || (!item1.uid)
+ || item1.id === null
+ || item1.id === undefined
+ || oldRepos.findIndex((item2: GitHubRepositoryModel) => item1.id === item2.id) === -1
+ ) {
+ isExitFn = false;
+ break;
+ }
+ }
+ } else {
+ isExitFn = false;
+ }
+
+ if (isExitFn) {
+ return null;
+ }
+
+ const result: GitHubRepositoryModel[] = newRepos.filter((item: GitHubRepositoryModel) => item && item.id !== null && item.id !== undefined);
+ const uids: string[] = [];
+
+ if (isArrayPreviousDataRepositories) {
+ result.forEach((item1: GitHubRepositoryModel) => {
+ const found: GitHubRepositoryModel = oldRepos.find((item2: GitHubRepositoryModel) => item1.id === item2.id);
+ if (found && found.uid) {
+ item1.uid = found.uid;
+ }
+ });
+ }
+
+ for (const item of result) {
+ if (!item.uid) {
+ const exitsRepoUid: string = await RepositoryModel.getRepositoryUidById(item.id);
+ if (exitsRepoUid) {
+ item.uid = exitsRepoUid;
+ } else {
+ item.uid = uuid();
+ }
+ }
+ uids.push(item.uid);
+ }
+
+ return {
+ repositories: {
+ ...newData.repositories,
+ data: result,
+ uids,
+ },
+ };
+}
+
+export const onUpdateUser: any = firestore
+ .document('users/{userUid}')
+ .onUpdate(async (change: Change, context: EventContext) => {
+
+ try {
+ const newData: DocumentData = change.after.data();
+ const previousData: DocumentData = change.before.data();
+
+ const result: any = await updateRepositories(newData, previousData);
+
+ // Then return a promise of a set operation to update the count
+ return result ? change.after.ref.update(result) : null;
+ } catch (err) {
+ Logger.error(err);
+ throw new Error(err);
+ }
+
+ });
diff --git a/scripts/deployment/dev.sh b/scripts/deployment/dev.sh
index f43759447..00f86638e 100644
--- a/scripts/deployment/dev.sh
+++ b/scripts/deployment/dev.sh
@@ -2,6 +2,7 @@
# FUNCTIONS
(cd functions; npm install)
+(cd functions/src/environments; sed -i 's/{{ GITHUB_WEBHOOK_SECRET }}/'$GITHUB_WEBHOOK_SECRET'/g' environment.ts)
(cd functions/src/environments; sed -i 's/{{ FIREBASE_FUNCTIONS_URL }}/us-central1-pipelinedashboard-dev/g' environment.ts)
# WEB
diff --git a/scripts/deployment/eddie.sh b/scripts/deployment/eddie.sh
index a76ac5959..2e398e192 100644
--- a/scripts/deployment/eddie.sh
+++ b/scripts/deployment/eddie.sh
@@ -2,6 +2,7 @@
# FUNCTIONS
(cd functions; npm install)
+(cd functions/src/environments; sed -i 's/{{ GITHUB_WEBHOOK_SECRET }}/'$GITHUB_WEBHOOK_SECRET'/g' environment.ts)
(cd functions/src/environments; sed -i 's/{{ FIREBASE_FUNCTIONS_URL }}/us-central1-pipelinedashboard-eddie/g' environment.ts)
# WEB
diff --git a/scripts/deployment/khush.sh b/scripts/deployment/khush.sh
index 4f1fa23e4..4a38f388b 100644
--- a/scripts/deployment/khush.sh
+++ b/scripts/deployment/khush.sh
@@ -2,6 +2,7 @@
# FUNCTIONS
(cd functions; npm install)
+(cd functions/src/environments; sed -i 's/{{ GITHUB_WEBHOOK_SECRET }}/'$GITHUB_WEBHOOK_SECRET'/g' environment.ts)
(cd functions/src/environments; sed -i 's/{{ FIREBASE_FUNCTIONS_URL }}/us-central1-pipelinedashboard-khush/g' environment.ts)
# WEB
diff --git a/scripts/deployment/prod.sh b/scripts/deployment/prod.sh
index 16f869933..0afc9b8ae 100644
--- a/scripts/deployment/prod.sh
+++ b/scripts/deployment/prod.sh
@@ -2,10 +2,11 @@
# FUNCTIONS
(cd functions; npm install)
+(cd functions/src/environments; sed -i 's/{{ GITHUB_WEBHOOK_SECRET }}/'$GITHUB_WEBHOOK_SECRET'/g' environment.ts)
(cd functions/src/environments; sed -i 's/{{ FIREBASE_FUNCTIONS_URL }}/us-central1-pipelinedashboard/g' environment.ts)
# WEB
-(cd web/src/environments; sed -i 's/x\.x\.x/v0.11.prod-'$TRAVIS_BUILD_NUMBER'-ALPHA/g' environment.prod.ts)
+(cd web/src/environments; sed -i 's/x\.x\.x/v0.11-'$TRAVIS_BUILD_NUMBER'-ALPHA/g' environment.prod.ts)
(cd web/src/environments; sed -i 's/{{ FIREBASE_API_KEY }}/'$FIREBASE_API_KEY_PROD'/g' environment.prod.ts)
(cd web/src/environments; sed -i 's/{{ FIREBASE_AUTH_DOMAIN }}/'$FIREBASE_AUTH_DOMAIN_PROD'/g' environment.prod.ts)
(cd web/src/environments; sed -i 's/{{ FIREBASE_DATABASE_URL }}/'$FIREBASE_DATABASE_URL_PROD'/g' environment.prod.ts)
diff --git a/web/package.json b/web/package.json
index 9848137d6..643499644 100644
--- a/web/package.json
+++ b/web/package.json
@@ -58,7 +58,7 @@
"rxjs": "6.5.2",
"tslib": "^1.9.0",
"tslint-etc": "^1.6.0",
- "uuid": "^3.3.2",
+ "uuid": "^3.3.3",
"web-animations-js": "2.3.1",
"zone.js": "0.8.29"
},
@@ -69,6 +69,7 @@
"@types/hammerjs": "2.0.36",
"@types/jasmine": "2.8.9",
"@types/node": "10.12.0",
+ "@types/uuid": "3.4.3",
"codelyzer": "4.5.0",
"concurrently": "^4.1.0",
"cypress": "^3.4.1",
diff --git a/web/src/app/core/services/project.service.ts b/web/src/app/core/services/project.service.ts
index 9463c1bd8..8094b8be8 100644
--- a/web/src/app/core/services/project.service.ts
+++ b/web/src/app/core/services/project.service.ts
@@ -117,14 +117,14 @@ export class ProjectService {
// This function add the repository in any project
// @TODO: move to repository service
- public saveRepositories(project: ProjectModel, repositories: string[]): Observable {
+ public saveRepositories(project: ProjectModel, repositories: RepositoryModel[]): Observable {
// remove webhook from unselected repo
if (project.repositories && project.repositories.length > 0) {
const remove: string[] = project.repositories
- .filter((uid: string) => repositories.findIndex((name: string) => uid === RepositoryModel.getUid(name)) === -1);
+ .filter((uid: string) => repositories.findIndex((repo: RepositoryModel) => uid === repo.uid) === -1);
const removeWebhooks: Observable[] = [];
- remove.forEach((element: string) => {
- const tmp: Observable = this.repositoryService.deleteGitWebhook(element).pipe(take(1));
+ remove.forEach((uid: string) => {
+ const tmp: Observable = this.repositoryService.deleteGitWebhook({ uid }).pipe(take(1));
removeWebhooks.push(tmp);
});
forkJoin(removeWebhooks).subscribe();
@@ -150,16 +150,20 @@ export class ProjectService {
return this.activityService
.start()
.pipe(
- mergeMap(() => forkJoin(...repositories.map((repository: string) => this.repositoryService.loadRepository(repository)))),
+ mergeMap(() => forkJoin(
+ ...repositories.map((repository: RepositoryModel) => this.repositoryService.loadRepository(repository))
+ )),
switchMap(() => this.afs
.collection('projects')
.doc(project.uid)
.set(
{
- repositories: repositories.map((repoUid: string) => new RepositoryModel(repoUid).uid),
+ repositories: repositories.map((repo: RepositoryModel) => repo.uid),
updatedOn: firebase.firestore.Timestamp.fromDate(new Date()),
},
- { merge: true })),
+ { merge: true }
+ )
+ ),
take(1)
);
}
diff --git a/web/src/app/core/services/repository.service.ts b/web/src/app/core/services/repository.service.ts
index cadbefed1..1121e55c0 100644
--- a/web/src/app/core/services/repository.service.ts
+++ b/web/src/app/core/services/repository.service.ts
@@ -37,10 +37,9 @@ export class RepositoryService {
}
// This function loads all the available repositories
- public loadRepository(fullName: string): Observable {
+ public loadRepository(repo: RepositoryModel): Observable {
const callable: any = this.fns.httpsCallable('findRepositoryInfo');
-
- return callable({ fullName, token: this.authService.profile.oauth.githubToken });
+ return callable({ repository: repo, token: this.authService.profile.oauth.githubToken });
}
public createGitWebhook(repo: RepositoryModel): Observable {
@@ -49,19 +48,24 @@ export class RepositoryService {
return callable({ repositoryUid: repo.uid, token: this.authService.profile.oauth.githubToken });
}
- public deleteGitWebhook(repositoryUid: string): Observable {
+ public deleteGitWebhook(repo: { uid?: string, id?: number }): Observable {
const callable: any = this.fns.httpsCallable('deleteGitWebhookRepository');
-
- return callable({ repositoryUid, token: this.authService.profile.oauth.githubToken });
+ return callable({ data: { uid: repo.uid, id: repo.id }, token: this.authService.profile.oauth.githubToken });
}
public getRating(repo: RepositoryModel): number {
- let rating: number = 0;
- const issuePoints: number = repo.issues.length > 0 ? this.getPoints(repo.issues[0].createdOn) : 0;
- const releasesPoints: number = repo.releases.length > 0 ? this.getPoints(repo.releases[0].createdOn) : 0;
- rating = (issuePoints + releasesPoints) / 2;
-
- return rating;
+ const checks: number[] = [];
+
+ checks.push(repo.issues.length > 0 ? this.getPoints(repo.issues[0].createdOn.toDate()) : 0);
+ checks.push(repo.releases.length > 0 ? this.getPoints(repo.releases[0].createdOn.toDate()) : 0);
+ checks.push(repo.milestones.length > 0 ? this.getPoints(new Date(repo.milestones[0].updatedAt)) : 0);
+ checks.push(repo.url ? 100 : 0);
+ checks.push(repo.description ? 100 : 0);
+ checks.push(repo.forksCount ? this.getPointsByCount(repo.forksCount, 50) : 0);
+ checks.push(repo.stargazersCount ? this.getPointsByCount(repo.stargazersCount, 100) : 0);
+ checks.push(repo.watchersCount ? this.getPointsByCount(repo.watchersCount, 25) : 0);
+
+ return checks.reduce((total: number, current: number) => total + current, 0) / checks.length;
}
public getPoints(date: Date): number {
@@ -78,4 +82,20 @@ export class RepositoryService {
return ((boundary - duration) / 30) * 100; // percentage
}
+
+ public getPointsByCount(count: number, limit: number): number {
+ let points: number;
+ switch (true) {
+ case (count >= 1 && count <= limit):
+ points = 50;
+ break;
+ case (count > limit):
+ points = 100;
+ break;
+ default:
+ points = 0;
+ }
+
+ return points;
+ }
}
diff --git a/web/src/app/projects/repository/repository.component.html b/web/src/app/projects/repository/repository.component.html
index 979ed5025..235ae3750 100644
--- a/web/src/app/projects/repository/repository.component.html
+++ b/web/src/app/projects/repository/repository.component.html
@@ -95,7 +95,7 @@ #{{ issue.number }} {{ issue.title }}
- {{ issue.updatedOn | timeAgo }}
+ {{ issue.updatedOn.toDate() | timeAgo }}
@@ -127,7 +127,7 @@ {{ pullRequest.title
{{ pullRequest.owner.username }}
- {{ pullRequest.createdOn | timeAgo }}
+ {{ pullRequest.createdOn.toDate() | timeAgo }}
@@ -160,7 +160,7 @@ {{ event.actor.username }}
- {{ event.createdOn | timeAgo }}
+ {{ event.createdOn.toDate() | timeAgo }}
@@ -192,7 +192,7 @@