diff --git a/.circleci/config.yml b/.circleci/config.yml index b813779aa7..621db64129 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,10 +33,10 @@ install_deploysuite: &install_deploysuite cp ./../buildscript/buildenv.sh . cp ./../buildscript/awsconfiguration.sh . restore_cache_settings_for_build: &restore_cache_settings_for_build - key: docker-node-modules-v4-{{ checksum "package-lock.json" }} + key: docker-node-modules-v3-{{ checksum "package-lock.json" }} save_cache_settings: &save_cache_settings - key: docker-node-modules-v4-{{ checksum "package-lock.json" }} + key: docker-node-modules-v3-{{ checksum "package-lock.json" }} paths: - node_modules @@ -73,7 +73,7 @@ jobs: command: | source awsenvconf source buildenvvar - ./master_deploy.sh -d ECS -e DEV -t latest -s dev_communityapp_taskvar -i communityapp -p FARGATE + ./master_deploy.sh -d ECS -e DEV -t latest -s dev_communityapp_taskvar -i communityapp # Build & Deploy against testing backend # "build-test": @@ -224,7 +224,7 @@ jobs: command: | source awsenvconf source buildenvvar - ./master_deploy.sh -d ECS -e PROD -t latest -s prod_communityapp_taskvar -i communityapp -p FARGATE + ./master_deploy.sh -d ECS -e PROD -t latest -s prod_communityapp_taskvar -i communityapp curl --request POST \ --url https://circleci.com/api/v2/project/github/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pipeline \ --header "Circle-Token: ${CIRCLE_TOKEN}" \ @@ -260,37 +260,37 @@ jobs: # path: ./automated-smoke-test/test-results # Automated Smoke Testing against Production - # Smoke-Testing-On-Production: - # <<: *defaults - # steps: - # # Initialization. - # - checkout - # - setup_remote_docker - # - run: *install_dependency - # - run: *install_deploysuite - # # Restoration of node_modules from cache. - # - restore_cache: *restore_cache_settings_for_build - # - run: - # name: "configuring environment" - # command: | - # ./awsconfiguration.sh PROD - # ./buildenv.sh -e PROD -b prod_communityapp_buildvar,prod_communityapp_deployvar - # - run: - # name: "Run automation" - # no_output_timeout: 20m - # command: | - # source awsenvconf - # source buildenvvar - # ./automated-smoke-test/smoketest.sh automation-config-prod.json prod - # - store_artifacts: - # path: ./automated-smoke-test/test-results + Smoke-Testing-On-Production: + <<: *defaults + steps: + # Initialization. + - checkout + - setup_remote_docker + - run: *install_dependency + - run: *install_deploysuite + # Restoration of node_modules from cache. + - restore_cache: *restore_cache_settings_for_build + - run: + name: "configuring environment" + command: | + ./awsconfiguration.sh PROD + ./buildenv.sh -e PROD -b prod_communityapp_buildvar,prod_communityapp_deployvar + - run: + name: "Run automation" + no_output_timeout: 20m + command: | + source awsenvconf + source buildenvvar + ./automated-smoke-test/smoketest.sh automation-config-prod.json prod + - store_artifacts: + path: ./automated-smoke-test/test-results # Test job for the cases when we do not need deployment. It just rapidly # installs (updates) app dependencies, and runs tests (ESLint, Stylelint, # Jest unit-tests). test: docker: - - image: circleci/node:10.24.1 + - image: circleci/node:8.11.1 steps: - checkout - restore_cache: @@ -360,7 +360,6 @@ workflows: - develop - TOP-1390 - PM-191-2 - - pm-199 # This is alternate dev env for parallel testing # Deprecate this workflow due to beta env shutdown # https://topcoder.atlassian.net/browse/CORE-251 diff --git a/Dockerfile b/Dockerfile index 0e7f8c6e2c..7691feebdc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,16 +2,12 @@ # and runs it against the specified Topcoder backend (development or # production) when container is executed. -FROM node:10.24.1 +FROM node:8.11.2 LABEL app="Community App" version="1.0" -RUN useradd -m -s /bin/bash appuser WORKDIR /opt/app COPY . . -RUN chown -R appuser:appuser /opt/app -USER appuser - ################################################################################ # Receiving of build arguments. diff --git a/__tests__/shared/components/Settings/Account/__snapshots__/index.jsx.snap b/__tests__/shared/components/Settings/Account/__snapshots__/index.jsx.snap new file mode 100644 index 0000000000..207666379e --- /dev/null +++ b/__tests__/shared/components/Settings/Account/__snapshots__/index.jsx.snap @@ -0,0 +1,2132 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders account setting page correctly 1`] = ` + +
+
+

+ Account information & Security +

+
+ + + +
+
+ + Save Changes + +
+
+`; diff --git a/__tests__/shared/components/Settings/Account/index.jsx b/__tests__/shared/components/Settings/Account/index.jsx new file mode 100644 index 0000000000..0bd72d407d --- /dev/null +++ b/__tests__/shared/components/Settings/Account/index.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import Renderer from 'react-test-renderer/shallow'; + +import Account from 'components/Settings/Account'; + +import userProfile from '../__mocks__/user-profile.json'; +import profileState from '../__mocks__/profile-state.json'; + +const rnd = new Renderer(); + +const settingsUI = { + TABS: { + ACCOUNT: { + MYACCOUNT: 'my account', + // LINKEDACCOUNT: 'linked account', + }, + }, +}; + +it('renders account setting page correctly', () => { + rnd.render(( {}} + clearIncorrectPassword={() => {}} + updateProfile={() => {}} + settingsUI={settingsUI} + />)); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); diff --git a/__tests__/shared/components/Settings/Header/__snapshots__/index.jsx.snap b/__tests__/shared/components/Settings/Header/__snapshots__/index.jsx.snap new file mode 100644 index 0000000000..bbd94fba03 --- /dev/null +++ b/__tests__/shared/components/Settings/Header/__snapshots__/index.jsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders setting page header correctly 1`] = ` +
+ + + SAVE CHANGES + + +
+`; diff --git a/__tests__/shared/components/Settings/Header/index.jsx b/__tests__/shared/components/Settings/Header/index.jsx new file mode 100644 index 0000000000..03de63ce50 --- /dev/null +++ b/__tests__/shared/components/Settings/Header/index.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import Renderer from 'react-test-renderer/shallow'; + +import Header from 'components/Settings/Header'; + +import profileState from '../__mocks__/profile-state.json'; + +const rnd = new Renderer(); + +it('renders setting page header correctly', () => { + rnd.render((
{}} + />)); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); diff --git a/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap b/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap new file mode 100644 index 0000000000..585e1c49bf --- /dev/null +++ b/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders email preferences setting page correctly 1`] = ` +
+ +
+`; diff --git a/__tests__/shared/components/Settings/Preferences/Email/index.jsx b/__tests__/shared/components/Settings/Preferences/Email/index.jsx new file mode 100644 index 0000000000..f49efaae8d --- /dev/null +++ b/__tests__/shared/components/Settings/Preferences/Email/index.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import Renderer from 'react-test-renderer/shallow'; + +import Email from 'components/Settings/Preferences/Email'; + +const rnd = new Renderer(); + +it('renders email preferences setting page correctly', () => { + rnd.render(()); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); diff --git a/__tests__/shared/components/Settings/__mocks__/profile-state.json b/__tests__/shared/components/Settings/__mocks__/profile-state.json new file mode 100644 index 0000000000..abd05d4852 --- /dev/null +++ b/__tests__/shared/components/Settings/__mocks__/profile-state.json @@ -0,0 +1,593 @@ +{ + "achievements": [ + { + "date": "2016-09-02T00:00:00.000-04:00", + "description": "iOS Developer Challenger Badge" + }, + { + "date": "2015-08-05T00:00:00.000-04:00", + "description": "Participant Badge" + }, + { + "date": "2015-08-05T00:00:00.000-04:00", + "description": "iOS Participant Badge" + }, + { + "date": "2014-07-01T00:00:00.000-04:00", + "description": "Digital Run Top Five" + }, + { + "date": "2014-07-01T00:00:00.000-04:00", + "description": "Digital Run Winner" + }, + { + "date": "2014-02-11T00:00:00.000-05:00", + "description": "First Milestone Prize" + }, + { + "date": "2013-03-15T00:00:00.000-04:00", + "description": "First Forum Post" + }, + { + "date": "2013-03-04T00:00:00.000-05:00", + "description": "First Passing Submission" + }, + { + "date": "2013-02-28T00:00:00.000-05:00", + "description": "First Placement" + }, + { + "date": "2013-02-28T00:00:00.000-05:00", + "description": "First Win" + }, + { + "date": "2012-11-09T00:00:00.000-05:00", + "description": "One Hundred Forum Posts" + }, + { + "date": "2010-10-09T00:00:00.000-04:00", + "description": "Five Hundred Forum Posts" + }, + { + "date": "2009-04-04T00:00:00.000-04:00", + "description": "Twenty Five Placements" + }, + { + "date": "2008-07-01T00:00:00.000-04:00", + "description": "Twenty Five First Placement Win" + }, + { + "date": "2008-06-11T00:00:00.000-04:00", + "description": "One Thousand Forum Posts" + }, + { + "date": "2008-05-12T00:00:00.000-04:00", + "description": "Fifty Passing Submissions" + }, + { + "date": "2008-05-03T00:00:00.000-04:00", + "description": "Fifty Placements" + }, + { + "date": "2008-05-01T00:00:00.000-04:00", + "description": "TopCoder Development Coder of the Month for May 2008" + }, + { + "date": "2007-03-30T00:00:00.000-04:00", + "description": "Fifty First Placement Win" + }, + { + "date": "2006-11-03T00:00:00.000-05:00", + "description": "One Hundred Passing Submissions" + }, + { + "date": "2006-10-28T00:00:00.000-04:00", + "description": "One Hundred First Placement Win" + }, + { + "date": "2006-10-28T00:00:00.000-04:00", + "description": "One hundred Placements" + }, + { + "date": "2006-10-28T00:00:00.000-04:00", + "description": "SRM Engagement Honor" + } + ], + "copilot": false, + "country": "China", + "info": null, + "loadingError": false, + "skills": { + "100": { + "tagName": ".NET", + "hidden": false, + "score": 2, + "sources": [ + "CHALLENGE" + ] + }, + "101": { + "tagName": "ActionScript", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "109": { + "tagName": "Android", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "110": { + "tagName": "AngularJS", + "hidden": false, + "score": 10, + "sources": [ + "CHALLENGE" + ] + }, + "115": { + "tagName": "Apache Kafka", + "hidden": false, + "score": 1, + "sources": [ + "USER_ENTERED" + ] + }, + "117": { + "tagName": "API", + "hidden": false, + "score": 4, + "sources": [ + "CHALLENGE" + ] + }, + "127": { + "tagName": "Blackberry SDK", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "132": { + "tagName": "C#", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "153": { + "tagName": "CSS", + "hidden": false, + "score": 10, + "sources": [ + "CHALLENGE" + ] + }, + "154": { + "tagName": "CSS3", + "hidden": false, + "score": 1, + "sources": [ + "USER_ENTERED" + ] + }, + "163": { + "tagName": "Docker", + "hidden": false, + "score": 5, + "sources": [ + "CHALLENGE" + ] + }, + "170": { + "tagName": "EJB", + "hidden": false, + "score": 18, + "sources": [ + "CHALLENGE" + ] + }, + "171": { + "tagName": "Elasticsearch", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "177": { + "tagName": "Express", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "182": { + "tagName": "Flex", + "hidden": false, + "score": 3, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + }, + "186": { + "tagName": "Force.Com Sites", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "211": { + "tagName": "Hibernate", + "hidden": false, + "score": 12, + "sources": [ + "CHALLENGE" + ] + }, + "212": { + "tagName": "HTML", + "hidden": false, + "score": 6, + "sources": [ + "CHALLENGE" + ] + }, + "213": { + "tagName": "HTML5", + "hidden": false, + "score": 12, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + }, + "214": { + "tagName": "HTTP", + "hidden": false, + "score": 3, + "sources": [ + "CHALLENGE" + ] + }, + "219": { + "tagName": "IBM DB2", + "hidden": false, + "score": 5, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + }, + "229": { + "tagName": "IBM Websphere Application Server", + "hidden": false, + "score": 3, + "sources": [ + "CHALLENGE" + ] + }, + "232": { + "tagName": "IBM WebSphere MQ", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "244": { + "tagName": "J2EE", + "hidden": false, + "score": 15, + "sources": [ + "CHALLENGE" + ] + }, + "247": { + "tagName": "Java", + "hidden": false, + "score": 123, + "sources": [ + "CHALLENGE" + ] + }, + "248": { + "tagName": "JavaScript", + "hidden": false, + "score": 20, + "sources": [ + "CHALLENGE" + ] + }, + "249": { + "tagName": "JBoss Seam", + "hidden": false, + "score": 3, + "sources": [ + "CHALLENGE" + ] + }, + "250": { + "tagName": "JDBC", + "hidden": false, + "score": 3, + "sources": [ + "CHALLENGE" + ] + }, + "254": { + "tagName": "JPA", + "hidden": false, + "score": 9, + "sources": [ + "CHALLENGE" + ] + }, + "255": { + "tagName": "jQuery", + "hidden": false, + "score": 2, + "sources": [ + "CHALLENGE" + ] + }, + "258": { + "tagName": "JSF", + "hidden": false, + "score": 2, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + }, + "259": { + "tagName": "JSON", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "260": { + "tagName": "JSP", + "hidden": false, + "score": 21, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + }, + "276": { + "tagName": "Maven", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "281": { + "tagName": "MongoDB", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "284": { + "tagName": "MySql", + "hidden": false, + "score": 7, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + }, + "286": { + "tagName": "Node.js", + "hidden": false, + "score": 18, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + }, + "295": { + "tagName": "Oracle Database", + "hidden": false, + "score": 14, + "sources": [ + "CHALLENGE" + ] + }, + "303": { + "tagName": "PHP", + "hidden": false, + "score": 1, + "sources": [ + "CHALLENGE" + ] + }, + "306": { + "tagName": "PostgreSQL", + "hidden": false, + "score": 8, + "sources": [ + "CHALLENGE" + ] + }, + "315": { + "tagName": "React.js", + "hidden": false, + "score": 1, + "sources": [ + "USER_ENTERED" + ] + }, + "323": { + "tagName": "REST", + "hidden": false, + "score": 5, + "sources": [ + "CHALLENGE" + ] + }, + "328": { + "tagName": "Salesforce", + "hidden": false, + "score": 2, + "sources": [ + "CHALLENGE" + ] + }, + "337": { + "tagName": "Servlet", + "hidden": false, + "score": 2, + "sources": [ + "CHALLENGE" + ] + }, + "344": { + "tagName": "Sharepoint 3.0", + "hidden": false, + "score": 2, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + }, + "354": { + "tagName": "Spring", + "hidden": false, + "score": 15, + "sources": [ + "CHALLENGE" + ] + }, + "355": { + "tagName": "SQL", + "hidden": false, + "score": 4, + "sources": [ + "CHALLENGE" + ] + }, + "356": { + "tagName": "SQL Server", + "hidden": false, + "score": 2, + "sources": [ + "CHALLENGE" + ] + }, + "362": { + "tagName": "Struts", + "hidden": false, + "score": 3, + "sources": [ + "CHALLENGE" + ] + }, + "387": { + "tagName": "Web Services", + "hidden": false, + "score": 8, + "sources": [ + "CHALLENGE" + ] + }, + "401": { + "tagName": "XML", + "hidden": false, + "score": 6, + "sources": [ + "CHALLENGE" + ] + }, + "402": { + "tagName": "XSL", + "hidden": false, + "score": 2, + "sources": [ + "CHALLENGE", + "USER_ENTERED" + ] + } + }, + "stats": null, + "profileForHandle": "tcscoder", + "linkedAccounts": [ + { + "userId": "623633", + "name": "tcscoder", + "email": "tcscoder@gmail.com", + "providerType": "github", + "provider": null, + "context": null, + "social": true, + "enterprise": false, + "emailVerified": false + }, + { + "userId": "1233342", + "name": "1233342", + "email": "", + "providerType": "stackoverflow", + "provider": null, + "context": null, + "social": true, + "enterprise": false, + "emailVerified": false + } + ], + "externalLinks": [ + { + "userId": 22655076, + "key": "4c7136cdbbf227274e49c475e7541abf", + "handle": "tcscoder", + "description": "Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for.", + "entities": null, + "keywords": null, + "title": "Google", + "images": "https://www.google.com/images/branding/googlelogo/1x/googlelogo_white_background_color_272x92dp.png", + "source": "embed.ly", + "synchronizedAt": 1525560141959, + "URL": "https://www.google.com" + } + ], + "externalAccounts": { + "userId": 22655076, + "handle": "tcscoder", + "behance": null, + "bitbucket": null, + "dribbble": null, + "github": null, + "linkedin": null, + "stackoverflow": null, + "twitter": null, + "updatedAt": null, + "createdAt": null, + "createdBy": null, + "updatedBy": null + }, + "emailPreferences": { + "TOPCODER_NL_DESIGN": true, + "TOPCODER_NL_DEV": true, + "TOPCODER_NL_GEN": true + }, + "credential": { + "activationCode": "SDUUH(HFS", + "resetToken": null, + "hasPassword": true + } +} \ No newline at end of file diff --git a/__tests__/shared/components/Settings/__mocks__/props-match.json b/__tests__/shared/components/Settings/__mocks__/props-match.json new file mode 100644 index 0000000000..3995d403ea --- /dev/null +++ b/__tests__/shared/components/Settings/__mocks__/props-match.json @@ -0,0 +1,8 @@ +{ + "isExact": true, + "params": { + "settingsTab": "profile" + }, + "path": "/settings/:settingsTab(profile|tools|account|preferences)", + "url": "/settings/profile" +} \ No newline at end of file diff --git a/__tests__/shared/components/Settings/__mocks__/user-profile.json b/__tests__/shared/components/Settings/__mocks__/user-profile.json new file mode 100644 index 0000000000..b2dd0838f0 --- /dev/null +++ b/__tests__/shared/components/Settings/__mocks__/user-profile.json @@ -0,0 +1,81 @@ +{ + "maxRating": { + "rating": 2162, + "track": "DEVELOP", + "subTrack": "DEVELOPMENT" + }, + "userId": 22655076, + "firstName": "Mike", + "lastName": "Smith", + "description": "Hello", + "otherLangName": "Som", + "handle": "tcscoder", + "handleLower": "tcscoder", + "status": "ACTIVE", + "email": "tcscoder@gmail.com", + "addresses": [ + { + "streetAddr1": "Street line 1, #432", + "streetAddr2": "", + "city": "Cheng Du", + "zip": "610101", + "stateCode": "Sichuan", + "type": "HOME", + "updatedAt": null, + "createdAt": null, + "createdBy": "", + "updatedBy": "" + } + ], + "homeCountryCode": "CHN", + "competitionCountryCode": "CHN", + "photoURL": "https://topcoder-prod-media.s3.amazonaws.com/member/profile/tcscoder-1525559661069.png", + "tracks": [ + "DEVELOP" + ], + "updatedAt": "2018-05-05T22:34Z", + "createdAt": "2006-10-06T08:49Z", + "createdBy": "22655076", + "updatedBy": "22655076", + "groups": [ + { + "id": "20000010", + "modifiedBy": null, + "modifiedAt": null, + "createdBy": "8547899", + "createdAt": "2017-09-11T13:54:47.000Z", + "name": "Blockchain", + "description": "Blockchain", + "privateGroup": true, + "selfRegister": true, + "subGroups": null, + "parentGroup": null + }, + { + "id": "20000013", + "modifiedBy": null, + "modifiedAt": null, + "createdBy": "8547899", + "createdAt": "2017-10-19T10:54:47.000Z", + "name": "Veterans", + "description": "Veterans", + "privateGroup": true, + "selfRegister": true, + "subGroups": null, + "parentGroup": null + }, + { + "id": "20000015", + "modifiedBy": null, + "modifiedAt": null, + "createdBy": "8547899", + "createdAt": "2017-11-06T15:49:35.000Z", + "name": "Cognitive", + "description": "Cognitive Community", + "privateGroup": true, + "selfRegister": true, + "subGroups": null, + "parentGroup": null + } + ] +} \ No newline at end of file diff --git a/__tests__/shared/components/Settings/__snapshots__/index.jsx.snap b/__tests__/shared/components/Settings/__snapshots__/index.jsx.snap new file mode 100644 index 0000000000..d73c732fee --- /dev/null +++ b/__tests__/shared/components/Settings/__snapshots__/index.jsx.snap @@ -0,0 +1,3042 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders account setting page correctly 1`] = ` +
+ +
+
+ + +
+
+`; + +exports[`renders preferences setting page correctly 1`] = ` +
+ +
+
+ + +
+
+`; + +exports[`renders profile setting page correctly 1`] = ` +
+ +
+
+ + +
+
+`; + +exports[`renders tools setting page correctly 1`] = ` +
+ +
+
+ + +
+
+`; diff --git a/__tests__/shared/components/Settings/index.jsx b/__tests__/shared/components/Settings/index.jsx new file mode 100644 index 0000000000..98fcde7a81 --- /dev/null +++ b/__tests__/shared/components/Settings/index.jsx @@ -0,0 +1,83 @@ +import React from 'react'; +import Renderer from 'react-test-renderer/shallow'; + +import Settings from 'components/Settings'; + +import userProfile from './__mocks__/user-profile.json'; +import profileState from './__mocks__/profile-state.json'; +import propsMatch from './__mocks__/props-match.json'; + +const rnd = new Renderer(); + +it('renders profile setting page correctly', () => { + rnd.render(( {}} + showXlBadge={() => {}} + profile={userProfile} + profileState={profileState} + settingsPageState={{}} + lookupData={{}} + updateProfile={() => {}} + uploadPhoto={() => {}} + deletePhoto={() => {}} + addSkill={() => {}} + hideSkill={() => {}} + match={propsMatch} + />)); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); + +it('renders account setting page correctly', () => { + rnd.render(( {}} + showXlBadge={() => {}} + profile={userProfile} + profileState={profileState} + settingsPageState={{}} + updatePassword={() => {}} + clearIncorrectPassword={() => {}} + updateProfile={() => {}} + match={propsMatch} + />)); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); + +it('renders tools setting page correctly', () => { + rnd.render(( {}} + showXlBadge={() => {}} + profile={userProfile} + profileState={profileState} + settingsPageState={{}} + match={propsMatch} + />)); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); + +it('renders preferences setting page correctly', () => { + rnd.render(( {}} + showXlBadge={() => {}} + profile={userProfile} + profileState={profileState} + settingsPageState={{}} + match={propsMatch} + />)); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); diff --git a/__tests__/shared/components/challenge-listing/Sidebar/__snapshots__/Footer.jsx.snap b/__tests__/shared/components/challenge-listing/Sidebar/__snapshots__/Footer.jsx.snap index d5a3513f5d..88fbc5b716 100644 --- a/__tests__/shared/components/challenge-listing/Sidebar/__snapshots__/Footer.jsx.snap +++ b/__tests__/shared/components/challenge-listing/Sidebar/__snapshots__/Footer.jsx.snap @@ -69,7 +69,7 @@ exports[`Matches shallow shapshot 1`] = ` className="src-shared-components-challenge-listing-Sidebar-Footer-___style__copyright___ghkHg" > Topcoder © - 2025 + 2024

`; diff --git a/__tests__/shared/components/tc-communities/NewsletterSignup.jsx b/__tests__/shared/components/tc-communities/NewsletterSignup.jsx new file mode 100644 index 0000000000..4fdbfe1737 --- /dev/null +++ b/__tests__/shared/components/tc-communities/NewsletterSignup.jsx @@ -0,0 +1,72 @@ +import React from 'react'; +import Rnd from 'react-test-renderer/shallow'; +import TU from 'react-dom/test-utils'; +import NewsletterSignup from 'components/tc-communities/NewsletterSignup'; + +const rnd = new Rnd(); + +test('Snapshot match', () => { + rnd.render(( + + )); + expect(rnd.getRenderOutput()).toMatchSnapshot(); + + rnd.render(( + + )); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); + +class Wrapper extends React.Component { + componentDidMount() {} + + render() { + return ( + + ); + } +} + +const instance = TU.renderIntoDocument(( + +)); + +describe('handle click', () => { + beforeEach(() => jest.clearAllMocks()); + + test('onTitleClick', () => { + const matches = TU.scryRenderedDOMComponentsWithTag(instance, 'button'); + expect(matches.length).toBe(1); + TU.Simulate.click(matches[0]); + }); +}); diff --git a/__tests__/shared/components/tc-communities/__snapshots__/NewsletterSignup.jsx.snap b/__tests__/shared/components/tc-communities/__snapshots__/NewsletterSignup.jsx.snap new file mode 100644 index 0000000000..dd6ae1c390 --- /dev/null +++ b/__tests__/shared/components/tc-communities/__snapshots__/NewsletterSignup.jsx.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Snapshot match 1`] = ` + +`; + +exports[`Snapshot match 2`] = ` + +`; diff --git a/__tests__/shared/components/tc-communities/communities/community-2/__snapshots__/Learn.jsx.snap b/__tests__/shared/components/tc-communities/communities/community-2/__snapshots__/Learn.jsx.snap index 07cfe014b4..922eede706 100644 --- a/__tests__/shared/components/tc-communities/communities/community-2/__snapshots__/Learn.jsx.snap +++ b/__tests__/shared/components/tc-communities/communities/community-2/__snapshots__/Learn.jsx.snap @@ -255,5 +255,19 @@ exports[`Match shadow snapshot 1`] = ` title="Useful Links" /> + `; diff --git a/__tests__/shared/components/tc-communities/communities/demo-expert/__snapshots__/Learn.jsx.snap b/__tests__/shared/components/tc-communities/communities/demo-expert/__snapshots__/Learn.jsx.snap index 22130a3c9f..442c1cebdf 100644 --- a/__tests__/shared/components/tc-communities/communities/demo-expert/__snapshots__/Learn.jsx.snap +++ b/__tests__/shared/components/tc-communities/communities/demo-expert/__snapshots__/Learn.jsx.snap @@ -410,5 +410,19 @@ exports[`Snapshot match 1`] = ` title="Useful Links" /> + `; diff --git a/__tests__/shared/components/tc-communities/communities/taskforce/__snapshots__/Home.jsx.snap b/__tests__/shared/components/tc-communities/communities/taskforce/__snapshots__/Home.jsx.snap index efb0463e56..ac2d5bdc21 100644 --- a/__tests__/shared/components/tc-communities/communities/taskforce/__snapshots__/Home.jsx.snap +++ b/__tests__/shared/components/tc-communities/communities/taskforce/__snapshots__/Home.jsx.snap @@ -212,5 +212,19 @@ exports[`Match shadow snapshot 1`] = ` } } /> + `; diff --git a/__tests__/shared/components/tc-communities/communities/tc-prod-dev/__snapshots__/Learn.jsx.snap b/__tests__/shared/components/tc-communities/communities/tc-prod-dev/__snapshots__/Learn.jsx.snap index cb16633b29..54022b665c 100644 --- a/__tests__/shared/components/tc-communities/communities/tc-prod-dev/__snapshots__/Learn.jsx.snap +++ b/__tests__/shared/components/tc-communities/communities/tc-prod-dev/__snapshots__/Learn.jsx.snap @@ -395,5 +395,19 @@ exports[`Snapshot match 1`] = ` title="Useful Links" /> + `; diff --git a/__tests__/shared/utils/__snapshots__/markdown.js.snap b/__tests__/shared/utils/__snapshots__/markdown.js.snap index 5dc7969857..449da8d15c 100644 --- a/__tests__/shared/utils/__snapshots__/markdown.js.snap +++ b/__tests__/shared/utils/__snapshots__/markdown.js.snap @@ -1203,7 +1203,7 @@ Array [ A list item with a code block:

- <code> + <code goes here> @@ -1294,7 +1294,7 @@ end tell ampersands and angle brackets. For example, this:

, - <div> + <div class="footer"> © 2004 Foo Corporation </div> diff --git a/automated-smoke-test/Dockerfile b/automated-smoke-test/Dockerfile new file mode 100644 index 0000000000..b228769e64 --- /dev/null +++ b/automated-smoke-test/Dockerfile @@ -0,0 +1,32 @@ +FROM node:10.17.0-stretch +RUN apt update +RUN apt install sudo +RUN sudo apt-get update; sudo apt-get install -y openjdk-8-jre openjdk-8-jre-headless openjdk-8-jdk openjdk-8-jdk-headless; +RUN curl --silent --show-error --location --fail --retry 3 --output /tmp/google-chrome-stable_current_amd64.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ + && (sudo dpkg -i /tmp/google-chrome-stable_current_amd64.deb || sudo apt-get -fy install) \ + && rm -rf /tmp/google-chrome-stable_current_amd64.deb \ + && sudo sed -i 's|HERE/chrome"|HERE/chrome" --disable-setuid-sandbox --no-sandbox|g' \ + "/opt/google/chrome/google-chrome" \ + && google-chrome --version +RUN export CHROMEDRIVER_RELEASE=$(curl --location --fail --retry 3 http://chromedriver.storage.googleapis.com/LATEST_RELEASE) \ + && curl --silent --show-error --location --fail --retry 3 --output /tmp/chromedriver_linux64.zip "http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_RELEASE/chromedriver_linux64.zip" \ + && cd /tmp \ + && unzip chromedriver_linux64.zip \ + && rm -rf chromedriver_linux64.zip \ + && sudo mv chromedriver /usr/local/bin/chromedriver \ + && sudo chmod +x /usr/local/bin/chromedriver \ + && chromedriver --version +RUN sudo apt-get install -y libgconf-2-4 +RUN sudo apt-get install -y xvfb +RUN sudo apt-get install -y jq +ENV DISPLAY :99 +RUN printf '#!/bin/sh\nXvfb :99 -screen 0 1280x1024x24 &\nexec "$@"\n' > /tmp/entrypoint \ + && chmod +x /tmp/entrypoint \ + && sudo mv /tmp/entrypoint /docker-entrypoint.sh + +COPY . /automated-smoke-test +WORKDIR /automated-smoke-test +RUN npm install +RUN ./node_modules/.bin/webdriver-manager update --versions.chrome=="$(google-chrome -version)" +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["/bin/sh"] \ No newline at end of file diff --git a/automated-smoke-test/config/automation-config-dev.json b/automated-smoke-test/config/automation-config-dev.json index 9ac9bf711d..4df4b85ad3 100644 --- a/automated-smoke-test/config/automation-config-dev.json +++ b/automated-smoke-test/config/automation-config-dev.json @@ -86,6 +86,7 @@ "allNotificationsUrl": "https://community-app.topcoder-dev.com/notifications", "policiesUrl": "https://community-app.topcoder-dev.com/policy", "username": "tester1234", + "password": "appirio123", "email": "sathya.jayabal@gmail.com", "challangesLinks": { "rssFeedUrl": "http://feeds.topcoder-dev.com/challenges/feed", diff --git a/automated-smoke-test/config/automation-config-local.json b/automated-smoke-test/config/automation-config-local.json index c2ca342f56..79f61ba285 100644 --- a/automated-smoke-test/config/automation-config-local.json +++ b/automated-smoke-test/config/automation-config-local.json @@ -81,6 +81,7 @@ "allNotificationsUrl": "http://localhost:3000/notifications", "policiesUrl": "http://localhost:3000/policy", "username": "Tonyj", + "password": "appirio123", "email": "topcoderconnect@gmail.com", "challangesLinks": { "rssFeedUrl": "http://feeds.topcoder.com/challenges/feed", diff --git a/automated-smoke-test/config/automation-config-prod.json b/automated-smoke-test/config/automation-config-prod.json index d05c56008c..ed2fc420de 100644 --- a/automated-smoke-test/config/automation-config-prod.json +++ b/automated-smoke-test/config/automation-config-prod.json @@ -81,6 +81,7 @@ "allNotificationsUrl": "https://www.topcoder.com/notifications", "policiesUrl": "https://www.topcoder.com/policy", "username": "CustomerUser", + "password": "appirio123", "email": "topcoderconnect@gmail.com", "challangesLinks": { "rssFeedUrl": "http://feeds.topcoder.com/challenges/feed", diff --git a/automated-smoke-test/test-data/test-data.json b/automated-smoke-test/test-data/test-data.json index 98fcf8a0bc..02aa602405 100644 --- a/automated-smoke-test/test-data/test-data.json +++ b/automated-smoke-test/test-data/test-data.json @@ -1,6 +1,7 @@ { "login": { - "invalidUsername": "gjhhvv" + "invalidUsername": "gjhhvv", + "invalidPassword": "invalidpassword" }, "tools": { "subscription": "Sample A", diff --git a/config/default.js b/config/default.js index f5424d3504..e30ec05227 100644 --- a/config/default.js +++ b/config/default.js @@ -132,8 +132,8 @@ module.exports = { INFO: { DESIGN_CHALLENGES: 'http://help.topcoder.com/hc/en-us/categories/202610437-DESIGN', DESIGN_CHALLENGE_CHECKPOINTS: 'https://help.topcoder.com/hc/en-us/articles/219240807-Multi-Round-Checkpoint-Design-Challenges', - DESIGN_CHALLENGE_SUBMISSION: 'https://www.topcoder.com/thrive/articles/Formatting%20Your%20Submission%20for%20Design%20Challenges', - DESIGN_CHALLENGE_TYPES: 'https://www.topcoder.com/thrive/articles/How%20To%20Compete%20in%20Design', + DESIGN_CHALLENGE_SUBMISSION: 'http://help.topcoder.com/hc/en-us/articles/219122667-Formatting-Your-Submission-for-Design-Challenges', + DESIGN_CHALLENGE_TYPES: 'http://help.topcoder.com/hc/en-us/articles/217481388-Choosing-a-Design-Challenge', RELIABILITY_RATINGS_AND_BONUSES: 'https://www.topcoder.com/thrive/articles/Development%20Reliability%20Ratings%20and%20Bonuses', STOCK_ART_POLICY: 'http://help.topcoder.com/hc/en-us/articles/217481408-Policy-for-Stock-Artwork-in-Design-Submissions', STUDIO_FONTS_POLICY: diff --git a/docs/contentful/AppComponent.md b/docs/contentful/AppComponent.md index a937f7b942..12c3980e17 100644 --- a/docs/contentful/AppComponent.md +++ b/docs/contentful/AppComponent.md @@ -46,6 +46,8 @@ Render top spots and list of competitors on specific TCO track. A block that fetches and renders a job listing page driven by recruitCRM. +### Type = `EmailSubscribeForm` + Generic subscribe for MailChimp tags component. - **listId** | **String (Required).** diff --git a/docs/contentful/custom-inline-components-in-markdown-fields.md b/docs/contentful/custom-inline-components-in-markdown-fields.md index 4961ffe946..da252c165b 100644 --- a/docs/contentful/custom-inline-components-in-markdown-fields.md +++ b/docs/contentful/custom-inline-components-in-markdown-fields.md @@ -69,6 +69,22 @@ other types too. | listId | | ID of MailChimp list to subscribe. | | interests | empty string | Optional. commas separated string of group ids to which user should be subscribed | +- #### NewsletterSignupForMembers + **Sample use:** `` + + Renders a newsletter signup button that takes user email from his profile + information. If the user is not-authenticated, it gets him to the login or + registration page, and subscribes him on return. Accepts the following props: + + | Param | Default | Description | + | --- | --- | --- | + | label | Subscribe for Newsletter | Optional. Custom label to show on the button. | + | listId | | ID of MailChimp list to subscribe. | + | tags | | ID of MailChimp tags to subscribe. | + | buttonTheme | primary-green-md | Theme key(`tc-` is omitted) for the button. See https://community-app.topcoder.com/examples/contentful/contentblock/3k7k1JpnSvIRrJYWs4izYi | + | title | Sign up for the Topcoder Newsletter | Modal title | + | desc | Do you want to subscribe to this newsletter? | Modal description | + - #### VideoModalButton *Example:* `` @@ -82,6 +98,14 @@ other types too. component works only with YouTube videos, and the URL should be similar to `https://www.youtube.com/embed/mD12LIqdxqk` (). +- #### NewsletterArchive + *Example:* `` + + A list of archive links sorted by descending `sent_date` from a MailChimp's campaign folder. Sould be working under any MarkdownParser component. + + The properties are: + - `name` - the unique name of the camplaing foler. It has to be only one name entity. If those duplicate first found will be picked up and rest ignored. + ## Links - #### Link diff --git a/docs/pwa/nginx/server.key b/docs/pwa/nginx/server.key new file mode 100644 index 0000000000..3e46bd6691 --- /dev/null +++ b/docs/pwa/nginx/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDXKekFdnLsjrnq +4RMF/V6ewrDRwzt9gVJXX8aEemq5wDjrnK1q2SJBrEMQw72T1oIt8TJs+d6HLc0K +DX3MILdix/r/+ML5bVjTd6HdzqXnbsd9omvZ3a43QKLxGCvqwe0iN8C7106++nik +3issH6qiQh5p5ERN8gApSyj0/PIu051YCNL8SpsqDiRHtWzZt07ncPFPueINXdRu +AEb7pod7tIvBnI2kudJx4d2AX+lE7eOhsvCr0eHS3/V03BlprAt3qw7c2ZwLxTYZ +D/l6NgCoy6AQfq9d7XikTVFx9ZKiuCPVS11PzI6G2pbx5Y6ewHdcfLnPVIDKPfMh +L3SjmDTNAgMBAAECggEAOLf3kVUUHn/RSrViSmXsF3XDHsiUWhVJG3dH4YxTrfua +BaIbpNrwSNecJkMzKlGVp365iDimDIRqVIgR7UmCjiuhYvC0lQPaMoSKyum6mjN9 +qwSx4ZCqaC5FxcBVc2EDnc2MpPew7m8gdnWKc+s1E+jSE5/00YdFu5zwgwRa4zSx +TquwlMGd6AcE1J5YbxYGJRztAl2VKguBJFcSnnFef8xY18GoEO1lt43ef9ijsfL3 +mBccrlLWQmc1CxM8zZ83lD1yW41bcwMhx7XCgRZjhDtNmbyWWJOYoFwojtAxFqG4 +VHFKjY+kNgIWjKZim84GA3tUPS/L832GXYk6SyC6kQKBgQD/HsnCryGTaitjwn+5 +FbPWlBnnGn1hxE36g0u1B3A/mQ3Jut949Dsm9vEoS1gHOQBCc5cE3RXNZ6iZdSDc +Lf9YzZnchIgjauU9bqkZVI2RSPnNOTjWjXhGtrZxrc4eckRWqbChmYX0qCxokwhb +leIdmJy7Y0Q6ttVGAHySoC/h7wKBgQDX59mKR1Gm7rKcEqW2aWhz878Xft/OHchh +THF9EUGHWXOYiKDAPxV6+xg2ZZaPUorMdp588iC3dd0YRn/KSkFlYWIgA8yYkjI7 +Al6pwGgOPw8k2/t1Rxfw2NUJr/19dAqMSyxDZ6W1OsGsdoAIgOMQxx12zENZpX6y +xcewsWNhAwKBgQC33vbHa/WlC4YONmZbfTrKUp+AouTvC86v2OU9qgjKrYL0e80I +ne3sHVqeEf915S08t5aGmNlX23f2ciamyjgZRsW324VLEYX7CsCxUvFdXt07fhxq +9jdTr+g6cmv2IaEDXPXC4qVbOcIX9LC3YYVAk3eSzu6j6pY4B63A99bK3QKBgQDE +d/mQiGe4BVxJA/sB7Bed9D9+7PhSAu4WBE79pVdBCFhVhHbrmjw8xgN5dKY2U8F0 +X7jHMDovWDTSY0zkUwABdkWppmtmpxrIcdacmDbYR+/K9dd0GDaj91ydTSXaJF94 +3Osxhz7WlNoqy0ak9kwqN1cLhMMA78VEfw/BLRqm6wKBgGUZNlOXQjcAcFjZ12Z4 +3X3tOWjwAPx2D5kT99Jh2BDgzjOci303MDnxI+mb6BQY32mz5+2OlD+bz2qYHVPp +9+Ir9WNbIl+DCUadRcPuYPAwPSftBEKzH901BBXHnbSkLSWocvLqYxUBjJ64KTh0 +2OTOU4OPRrct2tauUUyPRiBX +-----END PRIVATE KEY----- diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000000..19cc61f8d6 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,196 @@ +openapi: 3.0.0 +info: + title: Community APP + description: Community APP + version: 1.0.0 +servers: + - url: http://local.topcoder-dev.com:3000 +tags: + - name: Profile +paths: + /api/recruit/profile: + get: + tags: + - Profile + description: | + Get Profile of current user + + **Authorization** All topcoder members are allowed. + security: + - bearerAuth: [] + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Profile" + "401": + description: Not authenticated + content: + application/json: + schema: + $ref: "#/components/schemas/AuthError" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/AuthError" + "404": + description: Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/Profile" + "500": + description: Internal Server Error + content: + text/plain:: + schema: + type: string + post: + tags: + - Profile + description: | + Update Profile details of current user + **Authorization** All topcoder members are allowed. + security: + - bearerAuth: [] + requestBody: + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/ProfileUpdate" + responses: + "204": + description: OK + "400": + description: Bad request + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/JoiError" + - $ref: "#/components/schemas/Error" + "401": + description: Not authenticated + content: + application/json: + schema: + $ref: "#/components/schemas/AuthError" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/AuthError" + "404": + description: Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal Server Error + content: + text/plain:: + schema: + type: string +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Profile: + required: + - availability + properties: + phone: + type: string + description: "The phone number of the user" + example: "+1123226666" + resume: + type: string + description: "The resume of the user" + availability: + type: boolean + description: "The availability of the user" + default: true + example: true + skill: + type: string + description: "The candidate's skills separated via ," + example: "Java,Angular,SQL Server,JavaScript" + salaryExpectation: + type: integer + description: "The candidate expected salary" + hasProfile: + type: boolean + description: "Whether has profile for the user" + ProfileUpdate: + required: + - phone + - availability + - city + - countryName + properties: + phone: + type: string + description: "The phone number of the user" + example: "(123) 456-7890" + city: + type: string + description: "The member's city" + countryName: + type: string + description: "The member's country" + resume: + type: string + format: binary + description: "The resume file of the user" + availability: + type: boolean + description: "The availability of the user" + example: true + Error: + properties: + error: + type: boolean + example: true + status: + type: integer + example: 404 + url: + type: string + format: uri + errObj: + type: object + JoiError: + required: + - message + properties: + message: + type: string + AuthError: + properties: + version: + type: string + result: + type: object + properties: + success: + type: boolean + example: false + status: + type: integer + example: 403 + content: + type: object + properties: + message: + type: string diff --git a/package.json b/package.json index 3ed6237f7a..45c9302fe6 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ }, "homepage": "https://github.com/topcoder-platform/community-app#readme", "engines": { - "node": "^10.24.1", - "npm": "^6.14.12" + "node": "^8.11.2", + "npm": "^5.6.0" }, "dependencies": { "@hapi/joi": "^16.1.4", @@ -172,8 +172,7 @@ "url-parse": "^1.4.1", "uuid": "^3.3.2", "valid-url": "^1.0.9", - "xml2json": "^0.11.2", - "xss": "^1.0.15" + "xml2json": "^0.11.2" }, "devDependencies": { "@commitlint/cli": "^8.3.5", diff --git a/src/server/index.js b/src/server/index.js index 3ee3671a78..393a72b9a7 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -22,12 +22,14 @@ import { factory as reducerFactory } from 'reducers'; import { redux, server as serverFactory } from 'topcoder-react-utils'; import { getRates as getExchangeRates } from 'services/money'; import { toJson as xmlToJson } from 'utils/xml2json'; -import { promisify } from 'util'; import cdnRouter from './routes/cdn'; +import mailChimpRouter from './routes/mailchimp'; import mockDocuSignFactory from './__mocks__/docu-sign-mock'; import recruitCRMRouter from './routes/recruitCRM'; import mmLeaderboardRouter from './routes/mmLeaderboard'; +import gSheetsRouter from './routes/gSheet'; +import blogRouter from './routes/blog'; import feedsRouter from './routes/feeds'; /* Dome API for topcoder communities */ @@ -40,40 +42,9 @@ global.atob = atob; const CMS_BASE_URL = `https://app.contentful.com/spaces/${config.SECRET.CONTENTFUL.SPACE_ID}`; -const getTimestamp = async () => { - let timestamp; - try { - const filePath = path.resolve(__dirname, '../../.build-info'); - if (!filePath.startsWith(path.resolve(__dirname, '../../'))) { - throw new Error('Invalid file path detected'); - } - - const MAX_FILE_SIZE = 10 * 1024; // 10 KB max file size - const stats = await promisify(fs.stat)(filePath); - if (stats.size > MAX_FILE_SIZE) { - throw new Error('File is too large and may cause DoS issues'); - } - - const fileContent = await promisify(fs.readFile)(filePath, 'utf-8'); - - let tsData; - try { - tsData = JSON.parse(fileContent); - } catch (parseErr) { - throw new Error('Invalid JSON format in file'); - } - - if (!tsData || !tsData.timestamp) { - throw new Error('Timestamp is missing in the JSON file'); - } - - timestamp = moment(tsData.timestamp).valueOf(); - } catch (err) { - console.error('Error:', err.message); - } - - return timestamp; -}; +let ts = path.resolve(__dirname, '../../.build-info'); +ts = JSON.parse(fs.readFileSync(ts)); +ts = moment(ts.timestamp).valueOf(); const sw = `sw.js${process.env.NODE_ENV === 'production' ? '' : '?debug'}`; const swScope = '/challenges'; // we are currently only interested in improving challenges pages @@ -81,7 +52,7 @@ const swScope = '/challenges'; // we are currently only interested in improving const tcoPattern = new RegExp(/^tco\d{2}\.topcoder(?:-dev)?\.com$/i); const universalNavUrl = config.UNIVERSAL_NAV_URL; -const getExtraScripts = ts => [ +const EXTRA_SCRIPTS = [ `