diff --git a/.eslintrc.json b/.eslintrc.json
index 6d5aa89db7..b95b54b979 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -6,7 +6,8 @@
"eslint-plugin-import",
"eslint-plugin-jsdoc",
"eslint-plugin-deprecation",
- "eslint-plugin-unused-imports"
+ "unused-imports",
+ "eslint-plugin-lodash"
],
"overrides": [
{
@@ -202,7 +203,13 @@
"deprecation/deprecation": "warn",
"import/order": "off",
- "import/no-deprecated": "warn"
+ "import/no-deprecated": "warn",
+ "import/no-namespace": "error",
+ "unused-imports/no-unused-imports": "error",
+ "lodash/import-scope": [
+ "error",
+ "method"
+ ]
}
},
{
diff --git a/.gitattributes b/.gitattributes
index dfe0770424..406640bfcc 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,16 @@
-# Auto detect text files and perform LF normalization
+# By default, auto detect text files and perform LF normalization
+# This ensures code is always checked in with LF line endings
* text=auto
+
+# JS and TS files must always use LF for Angular tools to work
+# Some Angular tools expect LF line endings, even on Windows.
+# This ensures Windows always checks out these files with LF line endings
+# We've copied many of these rules from https://github.com/angular/angular-cli/
+*.js eol=lf
+*.ts eol=lf
+*.json eol=lf
+*.json5 eol=lf
+*.css eol=lf
+*.scss eol=lf
+*.html eol=lf
+*.svg eol=lf
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 04d426d091..c58e09edf2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,6 +6,9 @@ name: Build
# Run this Build for all pushes / PRs to current branch
on: [push, pull_request]
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
jobs:
tests:
runs-on: ubuntu-latest
@@ -29,11 +32,11 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
# https://github.com/actions/setup-node
- name: Install Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
@@ -58,7 +61,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache Yarn dependencies
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
# Cache entire Yarn cache directory (see previous step)
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -85,7 +88,7 @@ jobs:
# Upload coverage reports to Codecov (for one version of Node only)
# https://github.com/codecov/codecov-action
- name: Upload coverage to Codecov.io
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3
if: matrix.node-version == '16.x'
# Using docker-compose start backend using CI configuration
@@ -100,7 +103,7 @@ jobs:
# https://github.com/cypress-io/github-action
# (NOTE: to run these e2e tests locally, just use 'ng e2e')
- name: Run e2e tests (integration tests)
- uses: cypress-io/github-action@v2
+ uses: cypress-io/github-action@v4
with:
# Run tests in Chrome, headless mode
browser: chrome
@@ -116,7 +119,7 @@ jobs:
# Cypress always creates a video of all e2e tests (whether they succeeded or failed)
# Save those in an Artifact
- name: Upload e2e test videos to Artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
if: always()
with:
name: e2e-test-videos
@@ -125,7 +128,7 @@ jobs:
# If e2e tests fail, Cypress creates a screenshot of what happened
# Save those in an Artifact
- name: Upload e2e test failure screenshots to Artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
if: failure()
with:
name: e2e-test-screenshots
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 64303ca8bb..908c5c34fd 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -12,6 +12,9 @@ on:
- 'dspace-**'
pull_request:
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
jobs:
docker:
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
@@ -39,11 +42,11 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
- uses: docker/setup-buildx-action@v1
+ uses: docker/setup-buildx-action@v2
# https://github.com/docker/setup-qemu-action
- name: Set up QEMU emulation to build for multiple architectures
@@ -53,7 +56,7 @@ jobs:
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
- uses: docker/login-action@v1
+ uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -65,7 +68,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image
id: meta_build
- uses: docker/metadata-action@v3
+ uses: docker/metadata-action@v4
with:
images: dspace/dspace-angular
tags: ${{ env.IMAGE_TAGS }}
@@ -74,7 +77,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'dspace-angular' image
id: docker_build
- uses: docker/build-push-action@v2
+ uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml
index 6b9a273ab6..5d7c1c30f7 100644
--- a/.github/workflows/issue_opened.yml
+++ b/.github/workflows/issue_opened.yml
@@ -5,25 +5,22 @@ on:
issues:
types: [opened]
+permissions: {}
jobs:
automation:
runs-on: ubuntu-latest
steps:
# Add the new issue to a project board, if it needs triage
- # See https://github.com/marketplace/actions/create-project-card-action
- - name: Add issue to project board
+ # See https://github.com/actions/add-to-project
+ - name: Add issue to triage board
# Only add to project board if issue is flagged as "needs triage" or has no labels
# NOTE: By default we flag new issues as "needs triage" in our issue template
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
- uses: technote-space/create-project-card-action@v1
+ uses: actions/add-to-project@v0.3.0
# Note, the authentication token below is an ORG level Secret.
- # It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions
+ # It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
# This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific)
with:
- GITHUB_TOKEN: ${{ secrets.ORG_PROJECT_TOKEN }}
- PROJECT: DSpace Backlog
- COLUMN: Triage
- CHECK_ORG_PROJECT: true
- # Ignore errors
- continue-on-error: true
+ github-token: ${{ secrets.TRIAGE_PROJECT_TOKEN }}
+ project-url: https://github.com/orgs/DSpace/projects/24
diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml
index dcbab18f1b..a840a4fd17 100644
--- a/.github/workflows/label_merge_conflicts.yml
+++ b/.github/workflows/label_merge_conflicts.yml
@@ -5,21 +5,32 @@ name: Check for merge conflicts
# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
on:
push:
- branches:
- - main
+ branches: [ main ]
+ # So that the `conflict_label_name` is removed if conflicts are resolved,
+ # we allow this to run for `pull_request_target` so that github secrets are available.
+ pull_request_target:
+ types: [ synchronize ]
+
+permissions: {}
jobs:
triage:
+ # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
+ if: github.repository == 'dspace/dspace-angular'
runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
steps:
- # See: https://github.com/mschilde/auto-label-merge-conflicts/
+ # See: https://github.com/prince-chrismc/label-merge-conflicts-action
- name: Auto-label PRs with merge conflicts
- uses: mschilde/auto-label-merge-conflicts@v2.0
+ uses: prince-chrismc/label-merge-conflicts-action@v2
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
# Note, the authentication token is created automatically
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
with:
- CONFLICT_LABEL_NAME: 'merge conflict'
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # Ignore errors
- continue-on-error: true
+ conflict_label_name: 'merge conflict'
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ conflict_comment: |
+ Hi @${author},
+ Conflicts have been detected against the base branch.
+ Please [resolve these conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/about-merge-conflicts) as soon as you can. Thanks!
\ No newline at end of file
diff --git a/README.md b/README.md
index cef95a45fa..0ede4d4d19 100644
--- a/README.md
+++ b/README.md
@@ -179,7 +179,7 @@ If needing to update default configurations values for production, update local
- Update `environment.production.ts` file in `src/environment/` for a `production` environment;
-The environment object is provided for use as import in code and is extended with he runtime configuration on bootstrap of the application.
+The environment object is provided for use as import in code and is extended with the runtime configuration on bootstrap of the application.
> Take caution moving runtime configs into the buildtime configuration. They will be overwritten by what is defined in the runtime config on bootstrap.
@@ -351,7 +351,7 @@ Documentation
Official DSpace documentation is available in the DSpace wiki at https://wiki.lyrasis.org/display/DSDOC7x/
-Some UI specific configuration documentation is also found in the [`./docs`](docs) folder of htis codebase.
+Some UI specific configuration documentation is also found in the [`./docs`](docs) folder of this codebase.
### Building code documentation
diff --git a/config/config.example.yml b/config/config.example.yml
index 3f88b32324..27400f0041 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -14,6 +14,8 @@ ui:
rateLimiter:
windowMs: 60000 # 1 minute
max: 500 # limit each IP to 500 requests per windowMs
+ # Trust X-FORWARDED-* headers from proxies (default = true)
+ useProxies: true
# The REST API server settings
# NOTE: these settings define which (publicly available) REST API to use. They are usually
@@ -150,12 +152,24 @@ languages:
- code: fi
label: Suomi
active: true
+ - code: sv
+ label: Svenska
+ active: true
- code: tr
label: Türkçe
active: true
+ - code: kk
+ label: Қазақ
+ active: true
- code: bn
label: বাংলা
active: true
+ - code: hi
+ label: हिंदी
+ active: true
+ - code: el
+ label: Ελληνικά
+ active: true
# Browse-By Pages
browseBy:
@@ -165,6 +179,27 @@ browseBy:
fiveYearLimit: 30
# The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
defaultLowerLimit: 1900
+ # If true, thumbnail images for items will be added to BOTH search and browse result lists.
+ showThumbnails: true
+ # The number of entries in a paginated browse results list.
+ # Rounded to the nearest size in the list of selectable sizes on the
+ # settings menu.
+ pageSize: 20
+
+communityList:
+ # No. of communities to list per expansion (show more)
+ pageSize: 20
+
+homePage:
+ recentSubmissions:
+ # The number of item showing in recent submission components
+ pageSize: 5
+ # Sort record of recent submission
+ sortField: 'dc.date.accessioned'
+ topLevelCommunityList:
+ # No. of communities to list per page on the home page
+ # This will always round to the nearest number from the list of page sizes. e.g. if you set it to 7 it'll use 10
+ pageSize: 5
# Item Config
item:
@@ -240,7 +275,7 @@ themes:
# The default bundles that should always be displayed as suggestions when you upload a new bundle
bundle:
- - standardBundles: [ ORIGINAL, THUMBNAIL, LICENSE ]
+ standardBundles: [ ORIGINAL, THUMBNAIL, LICENSE ]
# Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video').
# For images, this enables a gallery viewer where you can zoom or page through images.
@@ -248,3 +283,16 @@ bundle:
mediaViewer:
image: false
video: false
+
+# Whether the end user agreement is required before users use the repository.
+# If enabled, the user will be required to accept the agreement before they can use the repository.
+# And whether the privacy statement should exist or not.
+info:
+ enableEndUserAgreement: true
+ enablePrivacyStatement: true
+
+# Whether to enable Markdown (https://commonmark.org/) and MathJax (https://www.mathjax.org/)
+# display in supported metadata fields. By default, only dc.description.abstract is supported.
+markdown:
+ enabled: false
+ mathjax: false
\ No newline at end of file
diff --git a/cypress/.gitignore b/cypress/.gitignore
index 99bd2a6312..645beff45f 100644
--- a/cypress/.gitignore
+++ b/cypress/.gitignore
@@ -1,2 +1,3 @@
screenshots/
videos/
+downloads/
diff --git a/cypress/integration/my-dspace.spec.ts b/cypress/integration/my-dspace.spec.ts
index fa923dbcbc..48f44eecb9 100644
--- a/cypress/integration/my-dspace.spec.ts
+++ b/cypress/integration/my-dspace.spec.ts
@@ -4,10 +4,11 @@ import { testA11y } from 'cypress/support/utils';
describe('My DSpace page', () => {
it('should display recent submissions and pass accessibility tests', () => {
- cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
-
cy.visit('/mydspace');
+ // This page is restricted, so we will be shown the login form. Fill it out & submit.
+ cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
+
cy.get('ds-my-dspace-page').should('exist');
// At least one recent submission should be displayed
@@ -36,10 +37,11 @@ describe('My DSpace page', () => {
});
it('should have a working detailed view that passes accessibility tests', () => {
- cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
-
cy.visit('/mydspace');
+ // This page is restricted, so we will be shown the login form. Fill it out & submit.
+ cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
+
cy.get('ds-my-dspace-page').should('exist');
// Click button in sidebar to display detailed view
@@ -61,9 +63,11 @@ describe('My DSpace page', () => {
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
it('should let you start a new submission & edit in-progress submissions', () => {
- cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.visit('/mydspace');
+ // This page is restricted, so we will be shown the login form. Fill it out & submit.
+ cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
+
// Open the New Submission dropdown
cy.get('button[data-test="submission-dropdown"]').click();
// Click on the "Item" type in that dropdown
@@ -131,9 +135,11 @@ describe('My DSpace page', () => {
});
it('should let you import from external sources', () => {
- cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.visit('/mydspace');
+ // This page is restricted, so we will be shown the login form. Fill it out & submit.
+ cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
+
// Open the New Import dropdown
cy.get('button[data-test="import-dropdown"]').click();
// Click on the "Item" type in that dropdown
diff --git a/cypress/integration/submission.spec.ts b/cypress/integration/submission.spec.ts
index 009c50115b..9eef596b02 100644
--- a/cypress/integration/submission.spec.ts
+++ b/cypress/integration/submission.spec.ts
@@ -6,11 +6,12 @@ describe('New Submission page', () => {
// NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts
it('should create a new submission when using /submit path & pass accessibility', () => {
- cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
-
// Test that calling /submit with collection & entityType will create a new submission
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
+ // This page is restricted, so we will be shown the login form. Fill it out & submit.
+ cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
+
// Should redirect to /workspaceitems, as we've started a new submission
cy.url().should('include', '/workspaceitems');
@@ -33,11 +34,12 @@ describe('New Submission page', () => {
});
it('should block submission & show errors if required fields are missing', () => {
- cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
-
// Create a new submission
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
+ // This page is restricted, so we will be shown the login form. Fill it out & submit.
+ cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
+
// Attempt an immediate deposit without filling out any fields
cy.get('button#deposit').click();
@@ -92,11 +94,12 @@ describe('New Submission page', () => {
});
it('should allow for deposit if all required fields completed & file uploaded', () => {
- cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
-
// Create a new submission
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
+ // This page is restricted, so we will be shown the login form. Fill it out & submit.
+ cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
+
// Fill out all required fields (Title, Date)
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
cy.get('input#dc_date_issued_year').type('2022');
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index 30951d46f1..04c217aa0f 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -19,6 +19,14 @@ declare global {
* @param password password to login as
*/
login(email: string, password: string): typeof login;
+
+ /**
+ * Login via form before accessing the next page. Useful to fill out login
+ * form when a cy.visit() call is to an a page which requires authentication.
+ * @param email email to login as
+ * @param password password to login as
+ */
+ loginViaForm(email: string, password: string): typeof loginViaForm;
}
}
}
@@ -26,6 +34,8 @@ declare global {
/**
* Login user via REST API directly, and pass authentication token to UI via
* the UI's dsAuthInfo cookie.
+ * WARNING: WHILE THIS METHOD WORKS, OCCASIONALLY RANDOM AUTHENTICATION ERRORS OCCUR.
+ * At this time "loginViaForm()" seems more consistent/stable.
* @param email email to login as
* @param password password to login as
*/
@@ -81,3 +91,20 @@ function login(email: string, password: string): void {
}
// Add as a Cypress command (i.e. assign to 'cy.login')
Cypress.Commands.add('login', login);
+
+
+/**
+ * Login user via displayed login form
+ * @param email email to login as
+ * @param password password to login as
+ */
+ function loginViaForm(email: string, password: string): void {
+ // Enter email
+ cy.get('ds-log-in [data-test="email"]').type(email);
+ // Enter password
+ cy.get('ds-log-in [data-test="password"]').type(password);
+ // Click login button
+ cy.get('ds-log-in [data-test="login-button"]').click();
+}
+// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
+Cypress.Commands.add('loginViaForm', loginViaForm);
\ No newline at end of file
diff --git a/cypress/support/index.ts b/cypress/support/index.ts
index 024b46cdde..70da23f044 100644
--- a/cypress/support/index.ts
+++ b/cypress/support/index.ts
@@ -24,7 +24,7 @@ import 'cypress-axe';
beforeEach(() => {
// Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie
// This just ensures it doesn't get in the way of matching other objects in the page.
- cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true}');
+ cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}');
});
// For better stability between tests, we visit "about:blank" (i.e. blank page) after each test.
diff --git a/package.json b/package.json
index dbb4cca8a5..705dc8e345 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "dspace-angular",
- "version": "0.0.0",
+ "version": "7.5.0-next",
"scripts": {
"ng": "ng",
"config:watch": "nodemon",
@@ -10,7 +10,7 @@
"start:prod": "yarn run build:prod && cross-env NODE_ENV=production yarn run serve:ssr",
"start:mirador:prod": "yarn run build:mirador && yarn run start:prod",
"preserve": "yarn base-href",
- "serve": "ng serve --configuration development",
+ "serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
"serve:ssr": "node dist/server/main",
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
"build": "ng build --configuration development",
@@ -78,6 +78,7 @@
"@nguniversal/express-engine": "^13.0.2",
"@ngx-translate/core": "^13.0.0",
"@nicky-lenaers/ngx-scroll-to": "^9.0.0",
+ "@types/grecaptcha": "^3.0.4",
"angular-idle-preload": "3.0.0",
"angulartics2": "^12.0.0",
"axios": "^0.27.2",
@@ -88,6 +89,8 @@
"compression": "^1.7.4",
"cookie-parser": "1.4.5",
"core-js": "^3.7.0",
+ "date-fns": "^2.29.3",
+ "date-fns-tz": "^1.3.7",
"deepmerge": "^4.2.2",
"express": "^4.17.1",
"express-rate-limit": "^5.1.3",
@@ -102,20 +105,21 @@
"json5": "^2.1.3",
"jsonschema": "1.4.0",
"jwt-decode": "^3.1.2",
- "klaro": "^0.7.10",
+ "klaro": "^0.7.18",
"lodash": "^4.17.21",
+ "markdown-it": "^13.0.1",
+ "markdown-it-mathjax3": "^4.3.1",
"mirador": "^3.3.0",
"mirador-dl-plugin": "^0.13.0",
"mirador-share-plugin": "^0.11.0",
- "moment": "^2.29.2",
"morgan": "^1.10.0",
"ng-mocks": "^13.1.1",
"ng2-file-upload": "1.4.0",
"ng2-nouislider": "^1.8.3",
"ngx-infinite-scroll": "^10.0.1",
- "ngx-moment": "^5.0.0",
"ngx-pagination": "5.0.0",
"ngx-sortablejs": "^11.1.0",
+ "ngx-ui-switch": "^11.0.1",
"nouislider": "^14.6.3",
"pem": "1.14.4",
"postcss-cli": "^9.1.0",
@@ -123,13 +127,13 @@
"react-copy-to-clipboard": "^5.0.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.5.5",
+ "sanitize-html": "^2.7.2",
"sortablejs": "1.13.0",
"tslib": "^2.0.0",
"url-parse": "^1.5.6",
"uuid": "^8.3.2",
"webfontloader": "1.6.28",
- "zone.js": "~0.11.5",
- "ngx-ui-switch": "^11.0.1"
+ "zone.js": "~0.11.5"
},
"devDependencies": {
"@angular-builders/custom-webpack": "~13.1.0",
@@ -155,16 +159,17 @@
"@types/js-cookie": "2.2.6",
"@types/lodash": "^4.14.165",
"@types/node": "^14.14.9",
+ "@types/sanitize-html": "^2.6.2",
"@typescript-eslint/eslint-plugin": "5.11.0",
"@typescript-eslint/parser": "5.11.0",
- "axe-core": "^4.3.3",
+ "axe-core": "^4.4.3",
"compression-webpack-plugin": "^9.2.0",
"copy-webpack-plugin": "^6.4.1",
"cross-env": "^7.0.3",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.4.1",
"cssnano": "^5.0.6",
- "cypress": "9.5.1",
+ "cypress": "9.7.0",
"cypress-axe": "^0.14.0",
"debug-loader": "^0.0.1",
"deep-freeze": "0.0.1",
@@ -173,6 +178,7 @@
"eslint-plugin-deprecation": "^1.3.2",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsdoc": "^38.0.6",
+ "eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-unused-imports": "^2.0.0",
"express-static-gzip": "^2.1.5",
"fork-ts-checker-webpack-plugin": "^6.0.3",
diff --git a/scripts/base-href.ts b/scripts/base-href.ts
index aee547b46d..7212e1c516 100644
--- a/scripts/base-href.ts
+++ b/scripts/base-href.ts
@@ -1,4 +1,4 @@
-import * as fs from 'fs';
+import { existsSync, writeFileSync } from 'fs';
import { join } from 'path';
import { AppConfig } from '../src/config/app-config.interface';
@@ -16,7 +16,7 @@ const appConfig: AppConfig = buildAppConfig();
const angularJsonPath = join(process.cwd(), 'angular.json');
-if (!fs.existsSync(angularJsonPath)) {
+if (!existsSync(angularJsonPath)) {
console.error(`Error:\n${angularJsonPath} does not exist\n`);
process.exit(1);
}
@@ -30,7 +30,7 @@ try {
angularJson.projects['dspace-angular'].architect.build.options.baseHref = baseHref;
- fs.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2) + '\n');
+ writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2) + '\n');
} catch (e) {
console.error(e);
}
diff --git a/scripts/env-to-yaml.ts b/scripts/env-to-yaml.ts
index c2dd1cf0ca..6e8153f4c1 100644
--- a/scripts/env-to-yaml.ts
+++ b/scripts/env-to-yaml.ts
@@ -1,5 +1,5 @@
-import * as fs from 'fs';
-import * as yaml from 'js-yaml';
+import { existsSync, writeFileSync } from 'fs';
+import { dump } from 'js-yaml';
import { join } from 'path';
/**
@@ -18,7 +18,7 @@ if (args[0] === undefined) {
const envFullPath = join(process.cwd(), args[0]);
-if (!fs.existsSync(envFullPath)) {
+if (!existsSync(envFullPath)) {
console.error(`Error:\n${envFullPath} does not exist\n`);
process.exit(1);
}
@@ -26,10 +26,10 @@ if (!fs.existsSync(envFullPath)) {
try {
const env = require(envFullPath).environment;
- const config = yaml.dump(env);
+ const config = dump(env);
if (args[1]) {
const ymlFullPath = join(process.cwd(), args[1]);
- fs.writeFileSync(ymlFullPath, config);
+ writeFileSync(ymlFullPath, config);
} else {
console.log(config);
}
diff --git a/scripts/serve.ts b/scripts/serve.ts
index bf5506b8bd..ee8570a45c 100644
--- a/scripts/serve.ts
+++ b/scripts/serve.ts
@@ -1,4 +1,4 @@
-import * as child from 'child_process';
+import { spawn } from 'child_process';
import { AppConfig } from '../src/config/app-config.interface';
import { buildAppConfig } from '../src/config/config.server';
@@ -7,8 +7,9 @@ const appConfig: AppConfig = buildAppConfig();
/**
* Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl
+ * Any CLI arguments given to this script are patched through to `ng serve` as well.
*/
-child.spawn(
- `ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl}`,
+spawn(
+ `ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl} ${process.argv.slice(2).join(' ')} --configuration development`,
{ stdio: 'inherit', shell: true }
);
diff --git a/scripts/sync-i18n-files.ts b/scripts/sync-i18n-files.ts
old mode 100755
new mode 100644
diff --git a/scripts/test-rest.ts b/scripts/test-rest.ts
index b2a3ebd1af..9066777c42 100644
--- a/scripts/test-rest.ts
+++ b/scripts/test-rest.ts
@@ -1,5 +1,5 @@
-import * as http from 'http';
-import * as https from 'https';
+import { request } from 'http';
+import { request as https_request } from 'https';
import { AppConfig } from '../src/config/app-config.interface';
import { buildAppConfig } from '../src/config/config.server';
@@ -20,9 +20,15 @@ console.log(`...Testing connection to REST API at ${restUrl}...\n`);
// If SSL enabled, test via HTTPS, else via HTTP
if (appConfig.rest.ssl) {
- const req = https.request(restUrl, (res) => {
+ const req = https_request(restUrl, (res) => {
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
- res.on('data', (data) => {
+ // We will keep reading data until the 'end' event fires.
+ // This ensures we don't just read the first chunk.
+ let data = '';
+ res.on('data', (chunk) => {
+ data += chunk;
+ });
+ res.on('end', () => {
checkJSONResponse(data);
});
});
@@ -33,9 +39,15 @@ if (appConfig.rest.ssl) {
req.end();
} else {
- const req = http.request(restUrl, (res) => {
+ const req = request(restUrl, (res) => {
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
- res.on('data', (data) => {
+ // We will keep reading data until the 'end' event fires.
+ // This ensures we don't just read the first chunk.
+ let data = '';
+ res.on('data', (chunk) => {
+ data += chunk;
+ });
+ res.on('end', () => {
checkJSONResponse(data);
});
});
diff --git a/server.ts b/server.ts
index 9fe03fe5b5..608c214076 100644
--- a/server.ts
+++ b/server.ts
@@ -19,14 +19,17 @@ import 'zone.js/node';
import 'reflect-metadata';
import 'rxjs';
-import axios from 'axios';
-import * as pem from 'pem';
-import * as https from 'https';
+/* eslint-disable import/no-namespace */
import * as morgan from 'morgan';
import * as express from 'express';
-import * as bodyParser from 'body-parser';
import * as compression from 'compression';
import * as expressStaticGzip from 'express-static-gzip';
+/* eslint-enable import/no-namespace */
+
+import axios from 'axios';
+import { createCertificate } from 'pem';
+import { createServer } from 'https';
+import { json } from 'body-parser';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
@@ -48,6 +51,7 @@ import { ServerAppModule } from './src/main.server';
import { buildAppConfig } from './src/config/config.server';
import { APP_CONFIG, AppConfig } from './src/config/app-config.interface';
import { extendEnvironmentWithAppConfig } from './src/config/config.util';
+import { logStartupMessage } from './startup-message';
/*
* Set path for the browser application's dist folder
@@ -75,6 +79,10 @@ export function app() {
*/
const server = express();
+ // Tell Express to trust X-FORWARDED-* headers from proxies
+ // See https://expressjs.com/en/guide/behind-proxies.html
+ server.set('trust proxy', environment.ui.useProxies);
+
/*
* If production mode is enabled in the environment file:
* - Enable Angular's production mode
@@ -105,7 +113,7 @@ export function app() {
* Add parser for request bodies
* See [morgan](https://github.com/expressjs/body-parser)
*/
- server.use(bodyParser.json());
+ server.use(json());
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', (_, options, callback) =>
@@ -261,7 +269,7 @@ function serverStarted() {
* @param keys SSL credentials
*/
function createHttpsServer(keys) {
- https.createServer({
+ createServer({
key: keys.serviceKey,
cert: keys.certificate
}, app).listen(environment.ui.port, environment.ui.host, () => {
@@ -281,6 +289,8 @@ function run() {
}
function start() {
+ logStartupMessage(environment);
+
/*
* If SSL is enabled
* - Read credentials from configuration files
@@ -313,7 +323,7 @@ function start() {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
- pem.createCertificate({
+ createCertificate({
days: 1,
selfSigned: true
}, (error, keys) => {
diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html
index 7ef02a76cf..2d87f21d26 100644
--- a/src/app/access-control/epeople-registry/epeople-registry.component.html
+++ b/src/app/access-control/epeople-registry/epeople-registry.component.html
@@ -45,7 +45,7 @@
-
+ 0 && !(searching$ | async)"
[paginationOptions]="config"
diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.ts b/src/app/access-control/epeople-registry/epeople-registry.component.ts
index b99304d037..55233d8173 100644
--- a/src/app/access-control/epeople-registry/epeople-registry.component.ts
+++ b/src/app/access-control/epeople-registry/epeople-registry.component.ts
@@ -238,7 +238,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
this.epersonService.deleteEPerson(ePerson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData) => {
if (restResponse.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', {name: ePerson.name}));
- this.reset();
} else {
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
}
diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html
index 41ae67423c..e9cc48aee3 100644
--- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html
+++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html
@@ -36,12 +36,12 @@
-
+