diff --git a/pom.xml b/pom.xml index 364a9c21a..0710865e4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,14 +5,14 @@ org.ihtsdo.mlds mlds war - 5.2.0 + 5.3.1-SNAPSHOT ihtsdo-mlds Maven Webapp http://maven.apache.org org.snomed snomed-parent-bom - 3.8.0 + 3.13.0-SNAPSHOT diff --git a/src/main/ad-hoc/minimal-dev-db.sql b/src/main/ad-hoc/minimal-dev-db.sql index 9ab7d9dca..d2787d227 100644 --- a/src/main/ad-hoc/minimal-dev-db.sql +++ b/src/main/ad-hoc/minimal-dev-db.sql @@ -1,15 +1,15 @@ use mlds; --- Inserting data into t_authority table -INSERT INTO t_authority (name) VALUES ('ROLE_ADMIN'); -INSERT INTO t_authority (name) VALUES ('ROLE_USER'); -INSERT INTO t_authority (name) VALUES ('ROLE_STAFF'); -INSERT INTO t_authority (name) VALUES ('ROLE_STAFF_SE'); -INSERT INTO t_authority (name) VALUES ('ROLE_STAFF_IHTSDO'); +-- Inserting data into authority table +INSERT INTO authority (name) VALUES ('ROLE_ADMIN'); +INSERT INTO authority (name) VALUES ('ROLE_USER'); +INSERT INTO authority (name) VALUES ('ROLE_STAFF'); +INSERT INTO authority (name) VALUES ('ROLE_STAFF_SE'); +INSERT INTO authority (name) VALUES ('ROLE_STAFF_IHTSDO'); --- Inserting data into t_user table -INSERT INTO t_user (login, user_id, password, first_name, last_name, email, activated, lang_key, activation_key, created_by, created_date, last_modified_by, last_modified_date) +-- Inserting data into user table +INSERT INTO user (login, user_id, password, first_name, last_name, email, activated, lang_key, activation_key, created_by, created_date, last_modified_by, last_modified_date) VALUES ('system', 1, '572d3b834f32347527d749bc1a41042c920682fc430febd380b4b6a0134f314fd381ce11c9a05abe', NULL, 'System', NULL, true, 'en', NULL, 'system', '2014-06-24 12:29:08', NULL, NULL), ('anonymousUser', 2, '4f54479f8290dfd503b72a654faf5d70593eab443993d87a79e14e5f7cda3eb7988423aa99090c9b', 'Anonymous', 'User', NULL, true, 'en', NULL, 'system', '2014-06-24 12:29:08', NULL, NULL), @@ -19,8 +19,8 @@ VALUES ('sweden', 6, 'e6b4dc6acaa889021ab202e417245c0880c66b254ad5758a298d310d15d38f1385bc0d82fc6f5e62', NULL, NULL, NULL, true, NULL, NULL, 'system', '2014-07-25 10:27:30', NULL, NULL); --- Inserting data into t_user_authority table -INSERT INTO t_user_authority (user_id, name) +-- Inserting data into user_authority table +INSERT INTO user_authority (user_id, name) VALUES (1, 'ROLE_ADMIN'), (1, 'ROLE_USER'), diff --git a/src/main/documentation/Data-management.md b/src/main/documentation/Data-management.md index a2573ee92..db3c72481 100644 --- a/src/main/documentation/Data-management.md +++ b/src/main/documentation/Data-management.md @@ -19,7 +19,7 @@ The system has a few important roots: - Table "member" defines IHTSDO members using the ISO 2 character country code as a key. The table also include an "IHTSDO" pseudo-member to own the international releases, and administer non-member country affiliates - Table "release_package" is the root for the various release versions and files. - Table "affiliate" is the root object for all affiliate data -- "t_user"" and the other t_* tables come from a base framework and implement users, permissions, and event logging. The t_user table has an "active" flag for disabling locally defined users. +- user, authority, user_authority, persistent_audit_event, persistent_audit_event_data and persistent_token tables come from a base framework and implement users, permissions, and event logging. The user table has an "active" flag for disabling locally defined users. ## Data Administration - New Member Country Most administrative activities can be accomplished via the user interface. One exception is the (infrequent) onboarding of a new member country. To do this: diff --git a/src/main/documentation/Syndication-feed.md b/src/main/documentation/Syndication-feed.md new file mode 100644 index 000000000..4edd3c729 --- /dev/null +++ b/src/main/documentation/Syndication-feed.md @@ -0,0 +1,36 @@ +# Syndication Feed + +The [MLDS Syndication Feed](https://mlds.ihtsdotools.org/api/feed) is a public XML feed which extends the [Atom Syndication Format](https://tools.ietf.org/html/rfc4287) as described in the [IHTSDO +termserver-syndication](https://github.com/IHTSDO/termserver-syndication) github repository documentation. + +The feed has been available since MLDS 4.0.3 was deployed on 20 September 2023. + +Related JIRA MLDS project tickets include: + +* [MLDS-987](https://jira.ihtsdotools.org/browse/MLDS-987) New: Atom syndication feed for MLDS +* [MLDS-1021](https://jira.ihtsdotools.org/browse/MLDS-1021) Syndication feed refinement +* [MLDS-1028](https://jira.ihtsdotools.org/browse/MLDS-1028) Syndication feed refinements +* [MLDS-1031](https://jira.ihtsdotools.org/browse/MLDS-1031) MLDS Syndication Feed: Improve consistency of alternate links length attribute value formatting +* [MLDS-1043](https://jira.ihtsdotools.org/browse/MLDS-1043) Missing dependencies in MLDS Syndication Feed +* [MLDS-1047](https://jira.ihtsdotools.org/browse/MLDS-1047) MLDS \[update\] : add Access-Control headers to Syndication feed +* [MLDS-1051](https://jira.ihtsdotools.org/browse/MLDS-1051) Syndication dependencies are not highlighted when about to be removed +* [MLDS-1131](https://jira.ihtsdotools.org/browse/MLDS-1131) MLDS Feed Authentication Breaking Change +* [MLDS-1190](https://jira.ihtsdotools.org/browse/MLDS-1190) Clarification: how are Archived releases handled in the syndication feeds? +* [MLDS-1197](https://jira.ihtsdotools.org/browse/MLDS-1197) Syndication Feed: data validation refinement + +## Specification + +The [MLDS-987 description](https://jira.ihtsdotools.org/browse/MLDS-987#descriptionmodule) referred directly to the [IHTSDO +termserver-syndication](https://github.com/IHTSDO/termserver-syndication) documentation as the requirements specification. + +## Testing + +Feed compliance verification in Dev and UAT has used the [W3C Feed Validation Service](https://validator.w3.org/feed/). + +## Additional information + +### Impact of [MLDS-1049](https://jira.ihtsdotools.org/browse/MLDS-1049) archiving features on syndication feed + +[MLDS-1049](https://jira.ihtsdotools.org/browse/MLDS-1049) added release archiving features to MLDS. The impact of these on syndication feed contents was queried in [MLDS-1190](https://jira.ihtsdotools.org/browse/MLDS-1190), and the response also summarised here: + +>The MLDS syndication feed only includes releases with published status, so the impact of archiving changes for [MLDS-1049](https://jira.ihtsdotools.org/browse/MLDS-1049) is to remove a previously published release from the syndication feed if it gets archived. This is also the impact of changing it to any other offline status. diff --git a/src/main/documentation/User-management.md b/src/main/documentation/User-management.md index d6b13bc88..ff6025167 100644 --- a/src/main/documentation/User-management.md +++ b/src/main/documentation/User-management.md @@ -1,3 +1,32 @@ -# Users +# User Management -The system uses to repositories to manage users. The first is a collection of local tables rooted at "t_user", and the second is the IHTSDO web authentication system (development default is https://usermanagement2.ihtsdotools.org/security-web/). Affiliate users are created in the local database, while administrative users are normally sourced from the web authentication system. +MLDS versions up to and including 6.x have a two-tiered authentication architecture, with multiple hierarchical access roles. + +Anonymous visitors are presented with the MLDS Public views. + +Visitors may register directly within MLDS, with account details saved in the t_user table of the MLDS database, and their email address as their login identity. + +Registered user accounts may then also be approved as an Affiliate, giving them access to the MLDS Affiliate features. + +An alternative authentication route uses Confluence/Crowd accounts which are associated with an existing MLDS account by having the same email address, but which use a separate username for login credentials. + +If a registered user logs into MLDS using their registered email address, they will be authenticated through their MLDS database account and presented with the MLDS Affiliate views (which extend the Public views). + +If a registered user logs into MLDS using their Confluence/Crowd credentials, and their account matches an MLDS user email address, they will be authorized as an MLDS Member, NRC Staff or Administrator and presented with relevant views/features. + +Note that logging in with email/password (MLDS database account) credentials will only present the Affiliate / Public views, even if the site visitor has appropriate Confluence/Crowd credentials. To see the Members/Staff/Administrator features/views, visitors must authenticate using their Confluence/Crowd username/password credentials. + +In increasing features/functionality order, the authentication/roles patterns are: +- Unauthenticated + - **Public** (anonymous, not authenticated) +- Authenticated with MLDS credentials + - **Affiliate (Pending)** (authorized access, but pending account approval as an Affiliate by an Administrator) + - **Affiliate** (authorized access and account approved as an Affiliate by an Administrator) +- Authenticated with Confluence/Crowd credentials + - **Member** (authorized if the account is in the MLDS Members access group, and account email matches an MLDS DB user account) + - **Staff** (authorized if the account is in a relevant MLDS NRC Staff access group, and account email matches an MLDS DB user account) + - **Administrator** (authorized if the account is in a relevant MLDS Admin access group, and account email matches an MLDS DB user account) + +A notable aspect of Confluence/Crowd user authorization is that an MLDS user should only be placed in a single named MLDS access group. If placed in multiple groups, MLDS access may be problematic. + +MLDS user accounts can be inactivated by Administrators, after which login attempts using their email addresses will fail to authenticate. diff --git a/src/main/documentation/schema/er.pdf b/src/main/documentation/schema/er.pdf index 3f5cff24f..f7db9c1cb 100644 Binary files a/src/main/documentation/schema/er.pdf and b/src/main/documentation/schema/er.pdf differ diff --git a/src/main/documentation/schema/er.png b/src/main/documentation/schema/er.png index 0879f5767..25581f920 100644 Binary files a/src/main/documentation/schema/er.png and b/src/main/documentation/schema/er.png differ diff --git a/src/main/documentation/schema/er.svg b/src/main/documentation/schema/er.svg index 2ebc0f404..980cae1b7 100644 --- a/src/main/documentation/schema/er.svg +++ b/src/main/documentation/schema/er.svg @@ -124,7 +124,7 @@ application_id - NUMERIC + BIGINT @@ -154,7 +154,7 @@ iso_code_2 - CHARACTER VARYING(2) + VARCHAR(2) @@ -214,21 +214,21 @@ id - CHARACTER VARYING(255) + VARCHAR(255) author - CHARACTER VARYING(255) + VARCHAR(255) filename - CHARACTER VARYING(255) + VARCHAR(255) @@ -258,7 +258,7 @@ domain_id - NUMERIC + BIGINT @@ -273,7 +273,7 @@ password_reset_token_id - CHARACTER VARYING(255) + VARCHAR(255) @@ -325,22 +325,22 @@ - t_user + user - login - CHARACTER VARYING(50) + user_id + BIGINT - t_authority + authority @@ -348,14 +348,14 @@ name - CHARACTER VARYING(255) + VARCHAR(255) - t_persistent_audit_event + persistent_audit_event @@ -370,7 +370,7 @@ - t_persistent_audit_event_data + persistent_audit_event_data @@ -385,14 +385,14 @@ name - CHARACTER VARYING(50) + VARCHAR(255) - t_persistent_token + persistent_token @@ -400,29 +400,29 @@ series - CHARACTER VARYING(255) + VARCHAR(255) - t_user_authority + user_authority - login - CHARACTER VARYING(50) + user_id + BIGINT name - CHARACTER VARYING(255) + VARCHAR(255) diff --git a/src/main/documentation/schema/mlds.ddl b/src/main/documentation/schema/mlds.ddl index 2018a0986..b55a896c0 100644 --- a/src/main/documentation/schema/mlds.ddl +++ b/src/main/documentation/schema/mlds.ddl @@ -57,17 +57,17 @@ CREATE TABLE `affiliate_details` ( `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `other_text` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `subtype` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `first_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `last_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `alternate_email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `third_email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `first_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `last_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `alternate_email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `third_email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `street` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `city` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `post` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `post` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `billing_street` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `billing_city` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `billing_post` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `billing_post` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `country_iso_code_2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `billing_country_iso_code_2` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `organization_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, @@ -139,6 +139,19 @@ CREATE TABLE `application` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `authority` +-- + +DROP TABLE IF EXISTS `authority`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `authority` ( + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + PRIMARY KEY (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `commercial_usage` -- @@ -293,33 +306,9 @@ DROP TABLE IF EXISTS `email_domain_blacklist`; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `email_domain_blacklist` ( `domain_id` bigint NOT NULL AUTO_INCREMENT, - `domainname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `domain_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, PRIMARY KEY (`domain_id`) -) ENGINE=InnoDB AUTO_INCREMENT=386731 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `event` --- - -DROP TABLE IF EXISTS `event`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `event` ( - `event_id` bigint NOT NULL, - `type` varchar(31) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - `description` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - `timestamp` timestamp NOT NULL, - `event_sub_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `principal` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `browser_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `browser_version` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `ip_address` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `locale` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `session_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `user_agent` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - PRIMARY KEY (`event_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; +) ENGINE=InnoDB AUTO_INCREMENT=384824 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -372,9 +361,9 @@ CREATE TABLE `member` ( `logo_file` bigint DEFAULT NULL, `staff_notification_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `promote_packages` bit(1) NOT NULL DEFAULT b'0', - `contactEmail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `memberOrgName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `memberOrgURL` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `contact_email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `member_org_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `member_org_url` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, PRIMARY KEY (`member_id`), UNIQUE KEY `member_name_unique` (`key`), KEY `member_file` (`license_file`), @@ -397,7 +386,75 @@ CREATE TABLE `password_reset_token` ( `user_id` bigint NOT NULL, PRIMARY KEY (`password_reset_token_id`), KEY `fk_password_reset_token_user_id` (`user_id`), - CONSTRAINT `fk_password_reset_token_user_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`user_id`) + CONSTRAINT `fk_password_reset_token_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `persistent_audit_event` +-- + +DROP TABLE IF EXISTS `persistent_audit_event`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `persistent_audit_event` ( + `event_id` bigint NOT NULL, + `principal` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `event_date` timestamp NULL DEFAULT NULL, + `event_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `application_id` bigint DEFAULT NULL, + `affiliate_id` bigint DEFAULT NULL, + `release_package_id` bigint DEFAULT NULL, + `release_version_id` bigint DEFAULT NULL, + `release_file_id` bigint DEFAULT NULL, + `commercial_usage_id` bigint DEFAULT NULL, + PRIMARY KEY (`event_id`), + KEY `idx_persistent_audit_event` (`principal`,`event_date`), + KEY `FK_persistent_audit_event_application` (`application_id`), + KEY `FK_persistent_audit_event_affiliate` (`affiliate_id`), + KEY `FK_persistent_audit_event_release_package` (`release_package_id`), + KEY `FK_persistent_audit_event_release_version` (`release_version_id`), + KEY `FK_persistent_audit_event_release_file` (`release_file_id`), + KEY `FK_persistent_audit_event_commercial_usage` (`commercial_usage_id`), + CONSTRAINT `FK_persistent_audit_event_commercial_usage` FOREIGN KEY (`commercial_usage_id`) REFERENCES `commercial_usage` (`commercial_usage_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `persistent_audit_event_data` +-- + +DROP TABLE IF EXISTS `persistent_audit_event_data`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `persistent_audit_event_data` ( + `event_id` bigint NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + PRIMARY KEY (`event_id`,`name`), + KEY `idx_persistent_audit_event_data` (`event_id`), + KEY `idx_persistent_audit_event_data_name` (`name`), + CONSTRAINT `FK_event_persistent_audit_event_data` FOREIGN KEY (`event_id`) REFERENCES `persistent_audit_event` (`event_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `persistent_token` +-- + +DROP TABLE IF EXISTS `persistent_token`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `persistent_token` ( + `series` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `token_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `token_date` date DEFAULT NULL, + `ip_address` varchar(39) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `user_id` bigint DEFAULT NULL, + PRIMARY KEY (`series`), + KEY `fk_persistent_token_user_id` (`user_id`), + CONSTRAINT `fk_persistent_token_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; /*!40101 SET character_set_client = @saved_cs_client */; @@ -416,7 +473,7 @@ CREATE TABLE `release_file` ( `download_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, `primary_file` bit(1) NOT NULL DEFAULT b'0', `md5_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `file_size` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `file_size` bigint DEFAULT NULL, PRIMARY KEY (`release_file_id`), KEY `FK_release_file_release_version` (`release_version_id`), CONSTRAINT `FK_release_file_release_version` FOREIGN KEY (`release_version_id`) REFERENCES `release_version` (`release_version_id`) @@ -441,12 +498,12 @@ CREATE TABLE `release_package` ( `licence_file` bigint DEFAULT NULL, `priority` int DEFAULT '-1', `copyrights` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, - `releasePackageURI` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `release_package_uri` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`release_package_id`), - KEY `FK_package_t_user` (`created_by`), KEY `release_package_member` (`member_id`), KEY `licence_file` (`licence_file`), + KEY `FK_package_user` (`created_by`), CONSTRAINT `licence_file` FOREIGN KEY (`licence_file`) REFERENCES `file` (`file_id`), CONSTRAINT `release_package_member` FOREIGN KEY (`member_id`) REFERENCES `member` (`member_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; @@ -471,120 +528,39 @@ CREATE TABLE `release_version` ( `inactive_at` timestamp NULL DEFAULT NULL, `release_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `summary` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin, - `versionDependentDerivativeURI` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `versionDependentURI` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `versionURI` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `version_dependent_derivative_uri` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `version_dependent_uri` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `version_uri` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `package_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `archive` bit(1) NOT NULL DEFAULT b'0', PRIMARY KEY (`release_version_id`), KEY `FK_release_version_release_package` (`release_package_id`), - KEY `FK_release_version_t_user` (`created_by`), + KEY `FK_release_version_user` (`created_by`), CONSTRAINT `FK_release_version_release_package` FOREIGN KEY (`release_package_id`) REFERENCES `release_package` (`release_package_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; /*!40101 SET character_set_client = @saved_cs_client */; -- --- Table structure for table `t_authority` +-- Table structure for table `user` -- -DROP TABLE IF EXISTS `t_authority`; +DROP TABLE IF EXISTS `user`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `t_authority` ( - `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - PRIMARY KEY (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `t_persistent_audit_event` --- - -DROP TABLE IF EXISTS `t_persistent_audit_event`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `t_persistent_audit_event` ( - `event_id` bigint NOT NULL, - `principal` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `event_date` timestamp NULL DEFAULT NULL, - `event_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `application_id` bigint DEFAULT NULL, - `affiliate_id` bigint DEFAULT NULL, - `release_package_id` bigint DEFAULT NULL, - `release_version_id` bigint DEFAULT NULL, - `release_file_id` bigint DEFAULT NULL, - `commercial_usage_id` bigint DEFAULT NULL, - PRIMARY KEY (`event_id`), - KEY `idx_persistent_audit_event` (`principal`,`event_date`), - KEY `FK_t_persistent_audit_event_application` (`application_id`), - KEY `FK_t_persistent_audit_event_affiliate` (`affiliate_id`), - KEY `FK_t_persistent_audit_event_release_package` (`release_package_id`), - KEY `FK_t_persistent_audit_event_release_version` (`release_version_id`), - KEY `FK_t_persistent_audit_event_release_file` (`release_file_id`), - KEY `FK_t_persistent_audit_event_commercial_usage` (`commercial_usage_id`), - CONSTRAINT `FK_t_persistent_audit_event_commercial_usage` FOREIGN KEY (`commercial_usage_id`) REFERENCES `commercial_usage` (`commercial_usage_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `t_persistent_audit_event_data` --- - -DROP TABLE IF EXISTS `t_persistent_audit_event_data`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `t_persistent_audit_event_data` ( - `event_id` bigint NOT NULL, - `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - PRIMARY KEY (`event_id`,`name`), - KEY `idx_persistent_audit_event_data` (`event_id`), - KEY `idx_persistent_audit_event_data_name` (`name`), - CONSTRAINT `FK_event_persistent_audit_event_data` FOREIGN KEY (`event_id`) REFERENCES `t_persistent_audit_event` (`event_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `t_persistent_token` --- - -DROP TABLE IF EXISTS `t_persistent_token`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `t_persistent_token` ( - `series` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - `token_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `token_date` date DEFAULT NULL, - `ip_address` varchar(39) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `user_id` bigint DEFAULT NULL, - PRIMARY KEY (`series`), - KEY `fk_persistent_token_user_id` (`user_id`), - CONSTRAINT `fk_persistent_token_user_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `t_user` --- - -DROP TABLE IF EXISTS `t_user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `t_user` ( - `login` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, - `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `first_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `last_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, +CREATE TABLE `user` ( + `login` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `first_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `last_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `activated` bit(1) NOT NULL DEFAULT b'1', `lang_key` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `activation_key` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - `created_by` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT 'system', + `activation_key` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, + `created_by` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `last_modified_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `last_modified_by` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `last_modified_date` timestamp NULL DEFAULT NULL, `user_id` bigint NOT NULL, `inactive_at` timestamp NULL DEFAULT NULL, @@ -596,33 +572,19 @@ CREATE TABLE `t_user` ( /*!40101 SET character_set_client = @saved_cs_client */; -- --- Table structure for table `t_user_authority` +-- Table structure for table `user_authority` -- -DROP TABLE IF EXISTS `t_user_authority`; +DROP TABLE IF EXISTS `user_authority`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `t_user_authority` ( +CREATE TABLE `user_authority` ( `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, `user_id` bigint NOT NULL, PRIMARY KEY (`user_id`,`name`), KEY `fk_authority_name` (`name`), - CONSTRAINT `fk_authority_name` FOREIGN KEY (`name`) REFERENCES `t_authority` (`name`), - CONSTRAINT `fk_user_authority_user_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `user_registration` --- - -DROP TABLE IF EXISTS `user_registration`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `user_registration` ( - `user_registration_id` bigint NOT NULL AUTO_INCREMENT, - `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, - PRIMARY KEY (`user_registration_id`) + CONSTRAINT `fk_authority_name` FOREIGN KEY (`name`) REFERENCES `authority` (`name`), + CONSTRAINT `fk_user_authority_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/src/main/documentation/schema/readme.txt b/src/main/documentation/schema/readme.txt index ace03f551..f79cd9c2f 100644 --- a/src/main/documentation/schema/readme.txt +++ b/src/main/documentation/schema/readme.txt @@ -6,7 +6,7 @@ Some notes: * Tables affiliate and member are important roots. -* t_user and the other t_* tables come from a base framework and implement users, permissions, and event logging. t_authority and t_user_authority will be obsolete once we finish integrating with the staff authentication service. +* user, authority, user_authority, persistent_audit_event, persistent_audit_event_data and persistent_token tables come from a base framework and implement users, permissions, and event logging. authority and user_authority will be obsolete once we finish integrating with the staff authentication service. * Tables "databasechangelog" and "databasechangeloglock" are bookkeeping for Liquibase which manages our db changes. diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/config/RemoteAuthenticationConfiguration.java b/src/main/java/ca/intelliware/ihtsdo/mlds/config/RemoteAuthenticationConfiguration.java index edef182b6..da1081c06 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/config/RemoteAuthenticationConfiguration.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/config/RemoteAuthenticationConfiguration.java @@ -3,27 +3,22 @@ import ca.intelliware.ihtsdo.mlds.security.ihtsdo.HttpAuthAdaptor; import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.context.EnvironmentAware; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @Configuration -public class RemoteAuthenticationConfiguration implements EnvironmentAware { - - - private Environment environment; - - @Override - public void setEnvironment(Environment environment) { - - this.environment = environment; - } +public class RemoteAuthenticationConfiguration { @Bean - public HttpAuthAdaptor httpAuthAdaptor() { + public HttpAuthAdaptor httpAuthAdaptor(Environment environment, + UserTokenStore userTokenStore, + RestTemplateBuilder restTemplateBuilder) { + Binder binder = Binder.get(environment); String url = binder.bind("webauth.url", String.class).orElse(null); - return new HttpAuthAdaptor(url); + + return new HttpAuthAdaptor(url, userTokenStore, restTemplateBuilder); } } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/config/SecurityConfiguration.java b/src/main/java/ca/intelliware/ihtsdo/mlds/config/SecurityConfiguration.java index b88a3950b..d5568cf5b 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/config/SecurityConfiguration.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/config/SecurityConfiguration.java @@ -2,9 +2,13 @@ import ca.intelliware.ihtsdo.mlds.security.*; import ca.intelliware.ihtsdo.mlds.security.ihtsdo.HttpAuthAuthenticationProvider; +import jakarta.servlet.SessionCookieConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -56,6 +60,27 @@ public static PasswordEncoder passwordEncoder() { return new StandardPasswordEncoder(); } + @Bean + public WebServerFactoryCustomizer forceSecureCookies() { + return factory -> factory.addConnectorCustomizers(connector -> { + connector.setScheme("https"); + connector.setSecure(true); + }); + } + + @Bean + public ServletContextInitializer servletContextInitializer() { + return servletContext -> { + SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig(); + sessionCookieConfig.setHttpOnly(true); + sessionCookieConfig.setSecure(true); + sessionCookieConfig.setName("JSESSIONID"); + sessionCookieConfig.setPath("/"); + }; + } + + + @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { logger.debug("Configuring Global Security"); diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/config/UserTokenStore.java b/src/main/java/ca/intelliware/ihtsdo/mlds/config/UserTokenStore.java new file mode 100644 index 000000000..5d2d83f90 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/config/UserTokenStore.java @@ -0,0 +1,23 @@ +package ca.intelliware.ihtsdo.mlds.config; + +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class UserTokenStore { + private final Map userCookieMap = new ConcurrentHashMap<>(); + + public void store(String username, String token) { + userCookieMap.put(username, token); + } + + public String get(String username) { + return userCookieMap.get(username); + } + + public void remove(String username) { + userCookieMap.remove(username); + } +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/config/init/ReleaseMasterConfigLoad.java b/src/main/java/ca/intelliware/ihtsdo/mlds/config/init/ReleaseMasterConfigLoad.java new file mode 100644 index 000000000..3f7b1bebd --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/config/init/ReleaseMasterConfigLoad.java @@ -0,0 +1,40 @@ +package ca.intelliware.ihtsdo.mlds.config.init; + +import ca.intelliware.ihtsdo.mlds.domain.ReleasePackageConfig; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageConfigRepository; +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class ReleaseMasterConfigLoad { + + private final ReleasePackageConfigRepository repository; + + public ReleaseMasterConfigLoad(ReleasePackageConfigRepository repository) { + this.repository = repository; + } + + @PostConstruct + public void loadDefaultsIfEmpty() { + if (repository.count() == 0) { + repository.saveAll(List.of( + create("ONLINE"), + create("ALPHA/BETA"), + create("OFFLINE"), + create("ALL") + )); + } + } + + private ReleasePackageConfig create(String releaseType) { + ReleasePackageConfig config = new ReleasePackageConfig(); + config.setReleaseType(releaseType); + config.setReleasePackageAccess("ALL"); + config.setReleasePermissionType("NOT_SELECTED"); + config.setUserList("[]"); + config.setActive(false); + return config; + } +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Affiliate.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Affiliate.java index 5ab95d598..f30b9d51c 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Affiliate.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Affiliate.java @@ -37,7 +37,7 @@ public class Affiliate extends BaseEntity { @GenericField(searchable = Searchable.YES) Long affiliateId; - Instant created = Instant.now(); + Instant created = Instant.now(); @JsonIgnore @Column(name="inactive_at") @@ -91,7 +91,18 @@ public String getHomeMemberKey() { @Column(name="standing_state") StandingState standingState = StandingState.APPLYING; - public void addCommercialUsage(CommercialUsage newEntryValue) { + @Column(name = "deactivated", nullable = false, columnDefinition = "BOOLEAN DEFAULT FALSE") + private boolean deactivated = false; + + @JsonIgnore + @Column(name="last_processed") + private Instant lastProcessed; + + @Enumerated(EnumType.STRING) + @Column(name="reason_for_deactivation") + private ReasonForDeactivation reasonForDeactivation; + + public void addCommercialUsage(CommercialUsage newEntryValue) { Validate.notNull(newEntryValue.getCommercialUsageId()); if (newEntryValue.affiliate != null) { @@ -123,6 +134,21 @@ public Affiliate() { } + + public ReasonForDeactivation getReasonForDeactivation() { + return reasonForDeactivation; + } + + public void setReasonForDeactivation(ReasonForDeactivation reasonForDeactivation) { + this.reasonForDeactivation = reasonForDeactivation; + } + public Instant getCreated() { + return created; + } + + public void setCreated(Instant created) { + this.created = created; + } //For Tests public Affiliate(Long affiliateId) { this.affiliateId = affiliateId; @@ -150,7 +176,13 @@ public void setApplication(PrimaryApplication application) { addApplication(application); } } + public Instant getLastProcessed() { + return lastProcessed; + } + public void setLastProcessed(Instant lastProcessed) { + this.lastProcessed = lastProcessed; + } public AffiliateDetails getAffiliateDetails() { return affiliateDetails; } @@ -211,4 +243,13 @@ public Instant getInactiveAt() { public void setInactiveAt(Instant inactiveAt) { this.inactiveAt = inactiveAt; } + + + public boolean isDeactivated() { + return deactivated; + } + + public void setDeactivated(boolean deactivated) { + this.deactivated = deactivated; + } } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/AffiliateDetails.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/AffiliateDetails.java index 9ac95f9b9..74a478e77 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/AffiliateDetails.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/AffiliateDetails.java @@ -9,6 +9,7 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField; import java.time.Instant; @@ -54,15 +55,15 @@ public class AffiliateDetails extends BaseEntity implements Cloneable { @FullTextField String lastName; - @FullTextField + @KeywordField String email; @Column(name="alternate_email") - @FullTextField + @KeywordField String alternateEmail; @Column(name="third_email") - @FullTextField + @KeywordField String thirdEmail; @Column(name="landline_number") diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Application.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Application.java index 3f65903da..274e85dfd 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Application.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Application.java @@ -79,11 +79,21 @@ public static enum ApplicationType { @Column(name="application_type", insertable=false, updatable=false) String applicationTypeValue; + @JsonIgnore + @Column(name="last_processed") + private Instant lastProcessed; + public Application() { } + public Instant getLastProcessed() { + return lastProcessed; + } + public void setLastProcessed(Instant lastProcessed) { + this.lastProcessed = lastProcessed; + } // For tests public Application(Long applicationId) { this.applicationId = applicationId; diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Authority.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Authority.java index 696f4f703..3a87f9bf9 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Authority.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Authority.java @@ -15,11 +15,11 @@ * An authority (a security role) used by Spring Security. */ @Entity -@Table(name = "T_AUTHORITY") +@Table(name = "authority") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class Authority implements Serializable { private static final long serialVersionUID = 1L; - + @NotNull @Size(min = 0, max = 50) @Id diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/CommercialUsage.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/CommercialUsage.java index 507b515fb..99e3cfb4e 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/CommercialUsage.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/CommercialUsage.java @@ -73,6 +73,10 @@ public class CommercialUsage extends BaseEntity { @Where(clause = "inactive_at IS NULL") Set countries = new HashSet<>() /*Sets.newHashSet()*/; + @JsonIgnore + @Column(name="last_processed") + private Instant lastProcessed; + public CommercialUsage() { } @@ -82,7 +86,13 @@ public CommercialUsage(Long commercialUsageId, Affiliate affiliate) { this.commercialUsageId = commercialUsageId; this.affiliate = affiliate; } + public Instant getLastProcessed() { + return lastProcessed; + } + public void setLastProcessed(Instant lastProcessed) { + this.lastProcessed = lastProcessed; + } public Long getCommercialUsageId() { return commercialUsageId; } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Member.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Member.java index 348a2bf32..b4e38a263 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Member.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/Member.java @@ -25,13 +25,13 @@ public class Member extends BaseEntity { @Column(name="created_at") Instant createdAt = Instant.now(); - @Column(name="contactEmail") + @Column(name="contact_email") public String contactEmail; - @Column(name="memberOrgName") + @Column(name="member_org_name") public String memberOrgName; - @Column(name="memberOrgURL") + @Column(name="member_org_url") public String memberOrgURL; @JsonIgnore @@ -59,6 +59,60 @@ public class Member extends BaseEntity { @Column(name="promote_packages") private Boolean promotePackages; + @Column(name = "pending_application") + private int pendingApplication; + + @Column(name = "invoices_pending") + private int invoicesPending; + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public Boolean getFooterActive() { + return footerActive; + } + + public void setFooterActive(Boolean footerActive) { + this.footerActive = footerActive; + } + + @Column(name = "language", length = 50) + private String language; + + @Column(name = "footer_active") + private Boolean footerActive; + public int getUsageReports() { + return usageReports; + } + + public void setUsageReports(int usageReports) { + this.usageReports = usageReports; + } + + public int getInvoicesPending() { + return invoicesPending; + } + + public void setInvoicesPending(int invoicesPending) { + this.invoicesPending = invoicesPending; + } + + public int getPendingApplication() { + return pendingApplication; + } + + public void setPendingApplication(int pendingApplication) { + this.pendingApplication = pendingApplication; + } + + @Column(name = "usage_reports") + private int usageReports; + public Member() {} public Member(String key, long memberId) { diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PermissionVisibilityResponse.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PermissionVisibilityResponse.java new file mode 100644 index 000000000..f9f2a2fd6 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PermissionVisibilityResponse.java @@ -0,0 +1,41 @@ +package ca.intelliware.ihtsdo.mlds.domain; + +public class PermissionVisibilityResponse { + + private boolean isMasterPermission; + private String permissionType; + private String releaseType; + + // Constructor + public PermissionVisibilityResponse(boolean isMasterPermission, String permissionType, String releaseType) { + this.isMasterPermission = isMasterPermission; + this.permissionType = permissionType; + this.releaseType = releaseType; + } + + // Getters and Setters + public boolean isMasterPermission() { + return isMasterPermission; + } + + public void setMasterPermission(boolean isMasterPermission) { + this.isMasterPermission = isMasterPermission; + } + + public String getPermissionType() { + return permissionType; + } + + public void setPermissionType(String permissionType) { + this.permissionType = permissionType; + } + + public String getReleaseType() { + return releaseType; + } + + public void setReleaseType(String releaseType) { + this.releaseType = releaseType; + } +} + diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PersistentAuditEvent.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PersistentAuditEvent.java index 605855ec3..cd1503bed 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PersistentAuditEvent.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PersistentAuditEvent.java @@ -16,7 +16,7 @@ */ @Entity -@Table(name = "T_PERSISTENT_AUDIT_EVENT") +@Table(name = "persistent_audit_event") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class PersistentAuditEvent extends BaseEntity { @@ -38,7 +38,7 @@ public class PersistentAuditEvent extends BaseEntity { @ElementCollection @MapKeyColumn(name="name") @Column(name="value") - @CollectionTable(name="T_PERSISTENT_AUDIT_EVENT_DATA", joinColumns=@JoinColumn(name="event_id")) + @CollectionTable(name="persistent_audit_event_data", joinColumns=@JoinColumn(name="event_id")) private Map data = new HashMap<>(); @Column(name="affiliate_id") diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PersistentToken.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PersistentToken.java index 7dc9215c2..98755cdbf 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PersistentToken.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/PersistentToken.java @@ -19,7 +19,7 @@ */ @Entity -@Table(name = "T_PERSISTENT_TOKEN") +@Table(name = "persistent_token") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class PersistentToken implements Serializable { private static final long serialVersionUID = 1L; diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReasonForDeactivation.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReasonForDeactivation.java new file mode 100644 index 000000000..0d4e90c88 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReasonForDeactivation.java @@ -0,0 +1,6 @@ +package ca.intelliware.ihtsdo.mlds.domain; + +public enum ReasonForDeactivation { + PRIMARYCONTACTEMAIL, + AUTODEACTIVATION; +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleaseFile.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleaseFile.java index 13e8f75bb..0e32e8ed0 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleaseFile.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleaseFile.java @@ -42,7 +42,7 @@ public class ReleaseFile extends BaseEntity { String md5Hash; @Column(name="file_size") - String fileSize; + Long fileSize; public ReleaseFile() { } @@ -102,11 +102,11 @@ public void setMd5Hash(String md5Hash) { this.md5Hash = md5Hash; } - public String getFileSize() { + public Long getFileSize() { return fileSize; } - public void setFileSize(String fileSize) { + public void setFileSize(Long fileSize) { this.fileSize = fileSize; } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackage.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackage.java index 09e89cda8..2d4688f60 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackage.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackage.java @@ -36,7 +36,7 @@ public class ReleasePackage extends BaseEntity { @Column(name="copyrights") String copyrights; - @Column(name="releasePackageURI") + @Column(name="release_package_uri") String releasePackageURI; @Column(name="updated_at") @@ -65,6 +65,10 @@ public class ReleasePackage extends BaseEntity { @Fetch(FetchMode.SELECT) Set releaseVersions = Sets.newHashSet(); + @Enumerated(EnumType.STRING) + @Column(name="permission_type") + private ReleasePermissionType permissionType; + public ReleasePackage() { } @@ -177,4 +181,12 @@ public Instant getUpdatedAt() { public void setUpdatedAt(Instant updatedAt) { this.updatedAt = updatedAt; } + + public ReleasePermissionType getPermissionType() { + return permissionType; + } + + public void setPermissionType(ReleasePermissionType permissionType) { + this.permissionType = permissionType; + } } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageAccess.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageAccess.java new file mode 100644 index 000000000..e8d6a2687 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageAccess.java @@ -0,0 +1,35 @@ +package ca.intelliware.ihtsdo.mlds.domain; + +import jakarta.persistence.*; + +@Entity +@Table(name = "release_package_access") +@IdClass(ReleasePackageAccessId.class) +public class ReleasePackageAccess { + + @Id + @Column(name = "release_package_id") + private Long releasePackageId; + + @Id + @Column(name = "user_id") + private Long userId; + + public Long getReleasePackageId() { + return releasePackageId; + } + + public void setReleasePackageId(Long releasePackageId) { + this.releasePackageId = releasePackageId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageAccessId.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageAccessId.java new file mode 100644 index 000000000..d6d2c44c3 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageAccessId.java @@ -0,0 +1,46 @@ +package ca.intelliware.ihtsdo.mlds.domain; + +import java.io.Serializable; +import java.util.Objects; + +public class ReleasePackageAccessId implements Serializable { + + private Long releasePackageId; + private Long userId; + + public ReleasePackageAccessId() {} + + public ReleasePackageAccessId(Long releasePackageId, Long userId) { + this.releasePackageId = releasePackageId; + this.userId = userId; + } + + public Long getReleasePackageId() { + return releasePackageId; + } + + public void setReleasePackageId(Long releasePackageId) { + this.releasePackageId = releasePackageId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ReleasePackageAccessId that = (ReleasePackageAccessId) o; + return Objects.equals(releasePackageId, that.releasePackageId) && Objects.equals(userId, that.userId); + } + + @Override + public int hashCode() { + return Objects.hash(releasePackageId, userId); + } +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageConfig.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageConfig.java new file mode 100644 index 000000000..b3952c359 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePackageConfig.java @@ -0,0 +1,78 @@ +package ca.intelliware.ihtsdo.mlds.domain; + +import jakarta.persistence.*; + +@Entity +@Table(name="release_config_master") +public class ReleasePackageConfig { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "hibernate_sequence_generator") + @SequenceGenerator(name = "hibernate_sequence_generator", sequenceName = "mlds.hibernate_sequence", allocationSize = 1) + @Column(name="config_id") + Long configId; + + @Column(name="release_type") + public String releaseType; + + @Column(name="release_package") + public String releasePackageAccess; + + @Column(name="release_permission_type") + public String releasePermissionType; + + @Column(name = "user_list", columnDefinition = "JSON") + private String userList; + + @Column(name = "is_active") + private Boolean isActive; + + + public Long getConfigId() { + return configId; + } + + public void setConfigId(Long configId) { + this.configId = configId; + } + + public String getReleaseType() { + return releaseType; + } + + public void setReleaseType(String releaseType) { + this.releaseType = releaseType; + } + + public String getReleasePackageAccess() { + return releasePackageAccess; + } + + public void setReleasePackageAccess(String releasePackageAccess) { + this.releasePackageAccess = releasePackageAccess; + } + + public String getReleasePermissionType() { + return releasePermissionType; + } + + public void setReleasePermissionType(String releasePermissionType) { + this.releasePermissionType = releasePermissionType; + } + + public String getUserList() { + return userList; + } + + public void setUserList(String userList) { + this.userList = userList; + } + + public Boolean getActive() { + return isActive; + } + + public void setActive(Boolean active) { + isActive = active; + } +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePermissionType.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePermissionType.java new file mode 100644 index 000000000..586cb747c --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleasePermissionType.java @@ -0,0 +1,10 @@ +package ca.intelliware.ihtsdo.mlds.domain; + +public enum ReleasePermissionType { + ADMIN_ONLY, + ADMIN_AND_STAFF, + ADMIN_STAFF_AFFILIATES, + ADMIN_STAFF_SELECTED_USERS, + EVERYONE, + NOT_SELECTED; +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleaseVersion.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleaseVersion.java index c98acbae5..674fb4070 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleaseVersion.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/ReleaseVersion.java @@ -50,13 +50,13 @@ public class ReleaseVersion extends BaseEntity { boolean online; - @Column(name="versionDependentDerivativeURI") + @Column(name="version_dependent_derivative_uri") String versionDependentDerivativeURI; - @Column(name="versionDependentURI") + @Column(name="version_dependent_uri") String versionDependentURI; - @Column(name="versionURI") + @Column(name="version_uri") String versionURI; @Column(name="published_at") diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/User.java b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/User.java index 134f72180..8b89acf3c 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/domain/User.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/domain/User.java @@ -18,10 +18,10 @@ * A user. */ @Entity -@Table(name = "T_USER") +@Table(name = "user") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Where(clause = "inactive_at IS NULL") -@SQLDelete(sql="UPDATE T_USER SET inactive_at = now() WHERE user_id = ?") +@SQLDelete(sql="UPDATE user SET inactive_at = now() WHERE user_id = ?") public class User extends AbstractAuditingEntity implements Serializable { private static final long serialVersionUID = 1L; @@ -72,11 +72,12 @@ public class User extends AbstractAuditingEntity implements Serializable { @Column(name="inactive_at") private Instant inactiveAt; - + @Column(name = "unsubscribe_key") + private String unsubscribeKey; @JsonIgnore @ManyToMany @JoinTable( - name = "T_USER_AUTHORITY", + name = "user_authority", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "name", referencedColumnName = "name")}) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @@ -98,7 +99,13 @@ public void setLogin(String login) { public String getPassword() { return password; } + public String getUnsubscribeKey() { + return unsubscribeKey; + } + public void setUnsubscribeKey(String unsubscribeKey) { + this.unsubscribeKey = unsubscribeKey; + } public void setPassword(String password) { this.password = password; } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklist.java b/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklist.java index 0cc7cf571..064e110f4 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklist.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklist.java @@ -13,14 +13,15 @@ public class DomainBlacklist extends BaseEntity { @Column(name="domain_id") private Long domainId; - String domainname; + @Column(name="domain_name") + String domainName; - public String getDomainname() { - return domainname; + public String getDomainName() { + return domainName; } - public void setDomainname(String domainname) { - this.domainname = domainname; + public void setDomainName(String domainName) { + this.domainName = domainName; } @Override diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklistRespository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklistRespository.java index d6420c1ec..6d889813c 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklistRespository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklistRespository.java @@ -9,6 +9,6 @@ public interface DomainBlacklistRespository extends JpaRepository { // extends PagingAndSortingRepository { - List findByDomainname(String domainName); + List findByDomainName(String domainName); } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklistService.java b/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklistService.java index 540c58ffe..82611965e 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklistService.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/registration/DomainBlacklistService.java @@ -11,25 +11,25 @@ public class DomainBlacklistService { @Resource DomainBlacklistRespository domainBlacklistRespository; - + public boolean isDomainBlacklisted(String email) { String requestEmailDomain = extractDomain(email); - - List result = domainBlacklistRespository.findByDomainname(requestEmailDomain); - + + List result = domainBlacklistRespository.findByDomainName(requestEmailDomain); + if (result.size() > 0) { return true; } - + return false; } private String extractDomain(String email) { String[] result; String delimiter = "@"; - + result = email.split(delimiter); - + return result[1]; } } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateDetailsRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateDetailsRepository.java index 335aa2848..2e371d08c 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateDetailsRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateDetailsRepository.java @@ -2,6 +2,15 @@ import ca.intelliware.ihtsdo.mlds.domain.AffiliateDetails; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository public interface AffiliateDetailsRepository extends JpaRepository { -} \ No newline at end of file + + @Query(value = "select * from affiliate_details where email= :email",nativeQuery = true) + List getAllAffiliateDetailsByEmail(String email); + +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateRepository.java index e270c6ddb..e84281eb6 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateRepository.java @@ -1,14 +1,14 @@ package ca.intelliware.ihtsdo.mlds.repository; -import ca.intelliware.ihtsdo.mlds.domain.Affiliate; -import ca.intelliware.ihtsdo.mlds.domain.Member; -import ca.intelliware.ihtsdo.mlds.domain.StandingState; +import ca.intelliware.ihtsdo.mlds.domain.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.time.Instant; import java.util.Collection; import java.util.List; @@ -35,13 +35,20 @@ public interface AffiliateRepository extends JpaRepository { Page findByHomeMemberAndTextQuery(@Param("homeMember") Member homeMember, @Param("q") String q, Pageable pageable); - @Query(value = "SELECT a FROM Affiliate a LEFT JOIN a.applications b" - + " WHERE a.homeMember = :homeMember " - + " OR b.member = :homeMember ") - Page findByHomeMember(@Param("homeMember") Member homeMember, Pageable pageable); + @Query(value = "SELECT a FROM Affiliate a LEFT JOIN a.applications b " + + "WHERE (a.homeMember = :homeMember OR b.member = :homeMember) " + + "AND a.deactivated = false") + Page findByHomeMember(@Param("homeMember") Member homeMember, Pageable pageable); Iterable findByStandingStateInAndCreatorNotNull(Collection standingState); + Page findAllByDeactivatedFalse(Pageable pageable); + Page findByStandingStateNotAndDeactivatedFalse(StandingState standingState, Pageable pageable); + Page findByStandingStateAndDeactivatedFalse(StandingState standingState, Pageable pageable); + + Page findByHomeMemberAndStandingStateAndDeactivatedFalse(Member homeMember, StandingState standingState, Pageable pageable); + Page findByHomeMemberAndStandingStateNotAndDeactivatedFalse(Member homeMember, StandingState standingState, Pageable pageable); + Page findByHomeMemberAndStandingState(Member homeMember, StandingState standingState, Pageable pageable); Page findByHomeMemberAndStandingStateNot(Member homeMember, StandingState standingState, Pageable pageable); @@ -87,4 +94,26 @@ public interface AffiliateRepository extends JpaRepository { + "AND a.application.approvalState = ca.intelliware.ihtsdo.mlds.domain.ApprovalState.APPROVED " ) Iterable findByUsersAndStandingStateInAndApprovedHomeMembership(@Param("standingStates") List standingStates, @Param("member") Member member); + + @Query(value = "select * from affiliate where affiliate_details_id = :affiliateDetailsId",nativeQuery = true) + Affiliate getAllAffiliateByAffiliateDetailsId(Long affiliateDetailsId); + + @Query(value = "SELECT * FROM mlds.affiliate where home_member_id=1 and standing_state='PENDING_INVOICE' and last_processed is null",nativeQuery = true) + List getIHTSDOPendingInvoices(); + + + @Query("SELECT a.id FROM Affiliate a WHERE a.id IN :affiliateIds AND a.deactivated = false") + List findActiveAffiliateIds(@Param("affiliateIds") List affiliateIds); + + @Modifying + @Query("UPDATE Affiliate a SET a.standingState = :standingState, a.reasonForDeactivation = :reason WHERE a.id = :affiliateId") + int updateAffiliateStandingStateAndDeactivationReason(@Param("affiliateId") Long affiliateId, + @Param("standingState") StandingState standingState, + @Param("reason") ReasonForDeactivation reason); + + @Modifying + @Query("UPDATE Affiliate a SET a.lastProcessed = :timestamp WHERE a.id IN :affiliateIds") + void updateLastProcessed(@Param("affiliateIds") List affiliateIds, @Param("timestamp") Instant timestamp); + + } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateSearchRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateSearchRepository.java index 31cf85f94..fde1396cc 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateSearchRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/AffiliateSearchRepository.java @@ -36,8 +36,11 @@ public Page findFullTextAndMember(String q, Member homeMember, Standi } else { result = searchSession.search(Affiliate.class) .where(f -> f.bool() + .should(f.wildcard().field("affiliateDetails.email").matching(q + "*")) + .should(f.wildcard().field("affiliateDetails.alternateEmail").matching(q + "*")) + .should(f.wildcard().field("affiliateDetails.thirdEmail").matching(q + "*")) .should(f.simpleQueryString() - .fields("affiliateDetails.firstName", "affiliateDetails.lastName", "affiliateDetails.email", "affiliateDetails.alternateEmail", "affiliateDetails.thirdEmail", "affiliateDetails.organizationName", "affiliateDetails.organizationType") + .fields("affiliateDetails.firstName", "affiliateDetails.lastName", "affiliateDetails.organizationName", "affiliateDetails.organizationType") .matching(q + "*")) ) .fetch(pageable.getPageSize()); @@ -47,6 +50,7 @@ public Page findFullTextAndMember(String q, Member homeMember, Standi // Apply filters based on homeMember and standingState List filteredList = resultList.stream() + .filter(affiliate -> !affiliate.isDeactivated()) .filter(affiliate -> isAffiliateMatching(affiliate, homeMember, standingState, standingStateNot)) .toList(); // Convert stream to List diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ApplicationRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ApplicationRepository.java index 65b3e1668..49aaa9509 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ApplicationRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ApplicationRepository.java @@ -6,8 +6,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.time.Instant; import java.util.Collection; import java.util.List; @@ -24,4 +28,16 @@ public interface ApplicationRepository extends JpaRepository Page findByMember(Member member, Pageable pageable); + @Query(value = "select * from application where member_id = :memberId",nativeQuery = true) + List findMemberById(Long memberId); + + @Modifying + @Query("UPDATE Application a SET a.lastProcessed = :timestamp WHERE a.affiliate.affiliateId IN :affiliateIds") + void updateLastProcessed(@Param("affiliateIds") List affiliateIds, @Param("timestamp") Instant timestamp); + + @Query(value = "SELECT * FROM mlds.application WHERE (approval_state = 'REJECTED' OR approval_state = 'CHANGE_REQUESTED' OR approval_state = 'NOT_SUBMITTED') AND inactive_at IS NULL AND last_processed IS NULL",nativeQuery = true) + List getAllApplication(); + + + } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/CommercialUsageRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/CommercialUsageRepository.java index 111b6d0bb..6da46ac10 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/CommercialUsageRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/CommercialUsageRepository.java @@ -6,9 +6,11 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.time.Instant; import java.time.LocalDate; import java.util.*; @@ -28,7 +30,7 @@ public interface CommercialUsageRepository extends JpaRepository findByNotStateAndEffectiveToIsNull(@Param("state") UsageReportState state, Pageable pageable); /*MLDS 985---To Download Commercial usage CSV files this below code is used*/ - @Query(value="SELECT commercial_usage.affiliate_id, m2.`key` as \"Member Key\", commercial_usage_entry.country_iso_code_2 as \"Deplyment Country\", commercial_usage_count.country_iso_code_2 as \"Affiliate Country\", commercial_usage.start_date, commercial_usage.end_date, affiliate.standing_state, affiliate.created, affiliate_details.agreement_type, concat (affiliate_details.first_name, ' ', affiliate_details.last_name) AS applicant, concat (affiliate_details.type, '-', affiliate_details.subtype) AS type, affiliate_details.organization_name, affiliate_details.organization_type, current_usage, planned_usage, purpose, implementation_status, other_activities, commercial_usage_count.snomed_practices, commercial_usage_count.hospitals_staffing_practices, commercial_usage_count.databases_per_deployment, commercial_usage_count.deployed_data_analysis_systems, COUNT(commercial_usage_entry.commercial_usage_id) AS hospitals FROM affiliate, affiliate_details, `member` m2, commercial_usage_count, commercial_usage LEFT JOIN commercial_usage_entry ON commercial_usage.commercial_usage_id = commercial_usage_entry.commercial_usage_id WHERE commercial_usage.affiliate_id = affiliate.affiliate_id and affiliate.home_member_id = m2.member_id AND affiliate.affiliate_details_id = affiliate_details.affiliate_details_id AND commercial_usage.commercial_usage_id = commercial_usage_count.commercial_usage_id AND commercial_usage.state = 'SUBMITTED' AND affiliate_details.email NOT LIKE '%ihtsdo.org%' GROUP BY commercial_usage.affiliate_id, m2.`key`,commercial_usage_entry.country_iso_code_2, commercial_usage_count.country_iso_code_2, commercial_usage.start_date, commercial_usage.end_date, affiliate.standing_state, affiliate.created, affiliate_details.first_name, affiliate_details.last_name, affiliate_details.organization_name, affiliate_details.organization_type, current_usage, planned_usage, purpose, implementation_status, other_activities, commercial_usage_count.snomed_practices, commercial_usage_count.hospitals_staffing_practices, commercial_usage_count.databases_per_deployment, commercial_usage_count.deployed_data_analysis_systems, affiliate_details.agreement_type, affiliate.notes_internal, affiliate_details.type, affiliate_details.subtype ORDER BY organization_name, commercial_usage.affiliate_id", nativeQuery = true) + @Query(value="SELECT commercial_usage.affiliate_id, m2.`key` as \"Member Key\", IFNULL(commercial_usage_entry.country_iso_code_2, '\\\\N') AS \"Deployment Country\", IFNULL(commercial_usage_count.country_iso_code_2, '\\\\N') AS \"Affiliate Country\", IFNULL(commercial_usage.start_date, '\\\\N') AS start_date, IFNULL(commercial_usage.end_date, '\\\\N') AS end_date, IFNULL(affiliate.standing_state, '\\\\N') AS standing_state, IFNULL(affiliate.created, '\\\\N') AS created, IFNULL(affiliate_details.agreement_type, '\\\\N') AS agreement_type, IFNULL(CONCAT(affiliate_details.first_name, ' ', affiliate_details.last_name), '\\\\N') AS applicant, IFNULL(CONCAT(affiliate_details.type, '-', affiliate_details.subtype), '\\\\N') AS type, IFNULL(affiliate_details.organization_name, '\\\\N') AS organization_name, IFNULL(affiliate_details.organization_type, '\\\\N') AS organization_type, IFNULL(current_usage, '\\\\N') AS current_usage, IFNULL(planned_usage, '\\\\N') AS planned_usage, IFNULL(purpose, '\\\\N') AS purpose, IFNULL(implementation_status, '\\\\N') AS implementation_status, IFNULL(other_activities, '\\\\N') AS other_activities, IFNULL(commercial_usage_count.snomed_practices, '\\\\N') AS snomed_practices, IFNULL(commercial_usage_count.hospitals_staffing_practices, '\\\\N') AS hospitals_staffing_practices, IFNULL(commercial_usage_count.databases_per_deployment, '\\\\N') AS databases_per_deployment, IFNULL(commercial_usage_count.deployed_data_analysis_systems, '\\\\N') AS deployed_data_analysis_systems, COUNT(commercial_usage_entry.commercial_usage_id) AS hospitals FROM affiliate, affiliate_details, `member` m2, commercial_usage_count, commercial_usage LEFT JOIN commercial_usage_entry ON commercial_usage.commercial_usage_id = commercial_usage_entry.commercial_usage_id WHERE commercial_usage.affiliate_id = affiliate.affiliate_id and affiliate.home_member_id = m2.member_id AND affiliate.affiliate_details_id = affiliate_details.affiliate_details_id AND commercial_usage.commercial_usage_id = commercial_usage_count.commercial_usage_id AND commercial_usage.state = 'SUBMITTED' AND affiliate_details.email NOT LIKE '%ihtsdo.org%' GROUP BY commercial_usage.affiliate_id, m2.`key`,commercial_usage_entry.country_iso_code_2, commercial_usage_count.country_iso_code_2, commercial_usage.start_date, commercial_usage.end_date, affiliate.standing_state, affiliate.created, affiliate_details.first_name, affiliate_details.last_name, affiliate_details.organization_name, affiliate_details.organization_type, current_usage, planned_usage, purpose, implementation_status, other_activities, commercial_usage_count.snomed_practices, commercial_usage_count.hospitals_staffing_practices, commercial_usage_count.databases_per_deployment, commercial_usage_count.deployed_data_analysis_systems, affiliate_details.agreement_type, affiliate.notes_internal, affiliate_details.type, affiliate_details.subtype ORDER BY organization_name, commercial_usage.affiliate_id;", nativeQuery = true) Collection findUsageReport(); /*MLDS 985---To Download Commercial usage CSV files this below code is used*/ @@ -41,4 +43,10 @@ public interface CommercialUsageRepository extends JpaRepository searchUsageReports(@Param("searchText") String searchText, @Param("state") UsageReportState state, Pageable pageable); + @Query(value="SELECT * FROM mlds.commercial_usage where state='NOT_SUBMITTED' and inactive_at is null and last_processed is null",nativeQuery = true) + List findByState(); + + @Modifying + @Query("UPDATE CommercialUsage a SET a.lastProcessed = :timestamp WHERE a.id IN :affiliateIds") + void updateLastProcessed(@Param("affiliateIds") List affiliateIds, @Param("timestamp") Instant timestamp); } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/MemberRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/MemberRepository.java index 10a9a10a1..f98971b90 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/MemberRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/MemberRepository.java @@ -2,9 +2,13 @@ import ca.intelliware.ihtsdo.mlds.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository public interface MemberRepository extends JpaRepository { public Member findOneByKey(String key); + + @Query(value = "select * from member where member_id = :memberId",nativeQuery = true) + Member findMemberById(Long memberId); } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/PersistenceAuditEventRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/PersistenceAuditEventRepository.java index db9a09589..87d3a331c 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/PersistenceAuditEventRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/PersistenceAuditEventRepository.java @@ -30,6 +30,6 @@ public interface PersistenceAuditEventRepository extends JpaRepository findTypeAndEventDate(@Param("start") Instant start, @Param("end") Instant end); - @Query("SELECT ae FROM PersistentAuditEvent ae WHERE ae.auditEventType = 'RELEASE_FILE_DOWNLOADED' AND ae.auditEventDate BETWEEN :start AND :end AND ae.affiliateId IS NOT NULL") + @Query("SELECT ae FROM PersistentAuditEvent ae WHERE ae.auditEventType = 'RELEASE_FILE_DOWNLOADED' AND ae.auditEventDate BETWEEN :start AND :end AND ae.affiliateId IS NOT NULL AND ae.principal NOT LIKE '%@ihtsdo.org' AND ae.principal NOT LIKE '%@snomed.org'") List findTypeAndEventDateWithAffiliateIdNotNull(@Param("start") Instant start, @Param("end") Instant end); } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageAccessRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageAccessRepository.java new file mode 100644 index 000000000..ab22a0f80 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageAccessRepository.java @@ -0,0 +1,41 @@ +package ca.intelliware.ihtsdo.mlds.repository; + +import ca.intelliware.ihtsdo.mlds.domain.ReleasePackageAccess; +import ca.intelliware.ihtsdo.mlds.domain.ReleasePackageAccessId; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +public interface ReleasePackageAccessRepository extends JpaRepository { + List findByReleasePackageId(Long releasePackageId); + + boolean existsByReleasePackageIdAndUserId(long releasePackageId, long userId); + + @Transactional + @Modifying + @Query("DELETE FROM ReleasePackageAccess rpa WHERE rpa.releasePackageId = :packageId") + void deleteReleasePackageAccessByPackageId(@Param("packageId") Long packageId); + + @Transactional + @Modifying + @Query("DELETE FROM ReleasePackageAccess rpa WHERE rpa.releasePackageId IN :packageIds") + void deleteReleasePackageAccessByPackageIds(@Param("packageIds") List packageIds); + + @Transactional + @Modifying + @Query("DELETE FROM ReleasePackageAccess rpa WHERE rpa.releasePackageId = :packageId AND rpa.userId = :userId") + void deleteReleasePackageAccessByPackageIdAndUserId(@Param("packageId") Long packageId, @Param("userId") Long userId); + + @Query(value = "SELECT u.login FROM user u JOIN release_package_access rpa ON u.user_id = rpa.user_id WHERE rpa.release_package_id = :releasePackageId",nativeQuery = true) + List findLoginsByReleasePackageId(@Param("releasePackageId") Long releasePackageId); + + @Query(value = "SELECT u.login FROM user u WHERE u.user_id IN :userIds", nativeQuery = true) + List findLoginsByUserIds(@Param("userIds") List userIds); + + + +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageConfigRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageConfigRepository.java new file mode 100644 index 000000000..e7a0c6b2a --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageConfigRepository.java @@ -0,0 +1,17 @@ +package ca.intelliware.ihtsdo.mlds.repository; + +import ca.intelliware.ihtsdo.mlds.domain.ReleasePackageConfig; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ReleasePackageConfigRepository extends JpaRepository { + + ReleasePackageConfig findByReleaseType(String releaseType); + + ReleasePackageConfig findByReleaseTypeIgnoreCase(String releaseType); + + List findByReleasePermissionTypeNot(String releasePermissionType); + + List findByReleasePermissionType(String releasePermissionType); +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageRepository.java index c68f41a57..9a521d705 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleasePackageRepository.java @@ -2,11 +2,29 @@ import ca.intelliware.ihtsdo.mlds.domain.Member; import ca.intelliware.ihtsdo.mlds.domain.ReleasePackage; +import ca.intelliware.ihtsdo.mlds.domain.ReleasePermissionType; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import java.util.List; public interface ReleasePackageRepository extends JpaRepository { List findByMemberOrderByPriorityDesc(Member member); + + + List findAllByReleasePackageIdIn(List ids); + + @Transactional + @Modifying + @Query("UPDATE ReleasePackage rp SET rp.permissionType = :permissionType WHERE rp.releasePackageId IN :packageIds") + void updatePermissionTypeForPackages(@Param("permissionType") ReleasePermissionType permissionType, @Param("packageIds") List packageIds); + + @Transactional + @Modifying + @Query("UPDATE ReleasePackage rp SET rp.permissionType = :permissionType") + void updatePermissionTypeForAllPackages(@Param("permissionType") ReleasePermissionType permissionType); } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleaseVersionRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleaseVersionRepository.java index c613cff03..5a3d96345 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleaseVersionRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/ReleaseVersionRepository.java @@ -2,21 +2,24 @@ import ca.intelliware.ihtsdo.mlds.domain.ReleaseVersion; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.ReleaseVersionCheckViewDTO; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.Collection; +import java.util.List; public interface ReleaseVersionRepository extends JpaRepository { - @Query(value="SELECT CONCAT(release_package.name, '-', release_version.name) AS title, release_file.download_url, member.memberOrgName, member.memberOrgURL, member.contactEmail, release_version.id, release_package.copyrights, release_version.updated_at, release_version.published_at, release_version.summary, release_package.releasePackageURI, release_version.versionURI, release_version.versionDependentURI, release_version.versionDependentDerivativeURI, release_package.release_package_id, release_version.release_version_id, release_file.release_file_id, release_file.primary_file, release_file.md5_hash, release_file.file_size, release_version.package_type FROM release_package JOIN release_version ON release_version.release_package_id = release_package.release_package_id JOIN release_file ON release_file.release_version_id = release_version.release_version_id JOIN member ON member.member_id = release_package.member_id WHERE release_version.release_type = 'online' AND release_package.releasePackageURI <> '' AND release_version.versionURI <> '' ",nativeQuery = true) + @Query(value="SELECT CONCAT(release_package.name, '-', release_version.name) AS title, release_file.download_url, member.member_org_name, member.member_org_url, member.contact_email, release_version.id, release_package.copyrights, release_version.updated_at, release_version.published_at, release_version.summary, release_package.release_package_uri, release_version.version_uri, release_version.version_dependent_uri, release_version.version_dependent_derivative_uri, release_package.release_package_id, release_version.release_version_id, release_file.release_file_id, release_file.primary_file, release_file.md5_hash, release_file.file_size, release_version.package_type FROM release_package JOIN release_version ON release_version.release_package_id = release_package.release_package_id JOIN release_file ON release_file.release_version_id = release_version.release_version_id JOIN member ON member.member_id = release_package.member_id WHERE release_version.release_type = 'online' AND release_package.release_package_uri <> '' AND release_version.version_uri <> '' ",nativeQuery = true) Collection listAtomFeed(); - @Query(value = "SELECT CASE WHEN :releaseVersionURI IS NULL OR TRIM(:releaseVersionURI) = '' THEN 0 ELSE CASE WHEN EXISTS (SELECT 1 FROM release_version WHERE (TRIM(versionDependentURI) = TRIM(:releaseVersionURI) OR TRIM(versionDependentDerivativeURI) = TRIM(:releaseVersionURI))) THEN 1 ELSE 0 END END AS is_present", nativeQuery = true) + @Query(value = "SELECT CASE WHEN :releaseVersionURI IS NULL OR TRIM(:releaseVersionURI) = '' THEN 0 ELSE CASE WHEN EXISTS (SELECT 1 FROM release_version rv JOIN release_package rp ON rv.release_package_id = rp.release_package_id WHERE (TRIM(rv.version_dependent_uri) = TRIM(:releaseVersionURI) OR TRIM(rv.version_dependent_derivative_uri) = TRIM(:releaseVersionURI)) AND rp.inactive_at IS NULL) THEN 1 ELSE 0 END END AS is_present;", nativeQuery = true) Long checkDependent(@Param("releaseVersionURI") String releaseVersionURI); - + @Query(value = "SELECT rv.name AS releaseVersionName, rv.release_package_id AS releasePackageId, rp.inactive_at AS inactiveAt FROM release_version rv JOIN release_package rp ON rv.release_package_id = rp.release_package_id WHERE rv.version_dependent_uri = :releaseVersionURI AND rp.inactive_at IS NULL", nativeQuery = true) + List getDependentVersionNames(@Param("releaseVersionURI") String releaseVersionURI); } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/UserRepository.java b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/UserRepository.java index 6721c785c..c90b4db28 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/repository/UserRepository.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/repository/UserRepository.java @@ -3,6 +3,7 @@ import ca.intelliware.ihtsdo.mlds.domain.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.time.LocalDate; import java.util.List; @@ -22,6 +23,8 @@ public interface UserRepository extends JpaRepository { User getUserByEmailIgnoreCase(String emailAddress); User findByLoginIgnoreCase(String login); + @Query("SELECT u FROM User u WHERE LOWER(u.login) = LOWER(:login)") + User findByLoginIgnoreCaseSafe(@Param("login") String login); List findByLoginIgnoreCaseIn(List logins); diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/security/AjaxLogoutSuccessHandler.java b/src/main/java/ca/intelliware/ihtsdo/mlds/security/AjaxLogoutSuccessHandler.java index 792224eb7..f377694cf 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/security/AjaxLogoutSuccessHandler.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/security/AjaxLogoutSuccessHandler.java @@ -3,9 +3,11 @@ import java.io.IOException; +import ca.intelliware.ihtsdo.mlds.config.UserTokenStore; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; @@ -16,15 +18,28 @@ */ @Component public class AjaxLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler - implements LogoutSuccessHandler { - + implements LogoutSuccessHandler { + + private final UserTokenStore userTokenStore; + + @Autowired + public AjaxLogoutSuccessHandler(UserTokenStore userTokenStore) { + this.userTokenStore = userTokenStore; + } @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { - + throws IOException, ServletException { + + if (authentication != null) { + String username = authentication.getName(); + userTokenStore.remove(username); + logger.info("Cleared stored token for user '{}'"); + } response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write("{\"message\": \"Logout successful\"}"); } } + diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/security/ihtsdo/HttpAuthAdaptor.java b/src/main/java/ca/intelliware/ihtsdo/mlds/security/ihtsdo/HttpAuthAdaptor.java index be104335a..796879ffc 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/security/ihtsdo/HttpAuthAdaptor.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/security/ihtsdo/HttpAuthAdaptor.java @@ -1,13 +1,11 @@ package ca.intelliware.ihtsdo.mlds.security.ihtsdo; +import ca.intelliware.ihtsdo.mlds.config.UserTokenStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; @@ -26,22 +24,24 @@ public class HttpAuthAdaptor implements HeaderConstants { private static final String PARAM_LOGIN_USERNAME = "login"; private static final String PARAM_LOGIN_PASSWORD = "password"; - + private UserTokenStore cookieTokenStore; private String queryUrl; private RestTemplate restTemplate; - private String storedCookie; @Value("${ims.cookie}") private String authenticatedCookieName; - public HttpAuthAdaptor(String url) { - queryUrl = url; - this.restTemplate = new RestTemplateBuilder() + // Constructor injection – no need for @Autowired + public HttpAuthAdaptor(@Value("${your.url.property}") String url, + UserTokenStore cookieTokenStore, + RestTemplateBuilder restTemplateBuilder) { + this.queryUrl = url; + this.cookieTokenStore = cookieTokenStore; + this.restTemplate = restTemplateBuilder .additionalMessageConverters(new MappingJackson2HttpMessageConverter()) .build(); } - String checkUsernameAndPasswordValid(String username, String password) throws IOException, IllegalStateException { Map requestBody = new HashMap<>(); requestBody.put(PARAM_LOGIN_USERNAME, username); @@ -71,22 +71,38 @@ private String recoverAuthenticationCookie(ResponseEntity response) { return null; } + public CentralAuthUserInfo getUserAccountInfo(String username, String authenticationCookie) throws IOException { - HttpHeaders headers = new HttpHeaders(); - if (authenticationCookie != null) { - headers.add("Cookie", authenticationCookie); - storedCookie = authenticationCookie; - } else if (storedCookie != null) { - headers.add("Cookie", storedCookie); + String tokenToUse; + if (authenticationCookie != null && !authenticationCookie.isBlank()) { + tokenToUse = authenticationCookie; + cookieTokenStore.store(username, authenticationCookie); + } else { + tokenToUse = cookieTokenStore.get(username); + if (tokenToUse == null || tokenToUse.isBlank()) { + throw new IOException("Missing authentication cookie for user " + username + " and no fallback token found."); + } } + HttpHeaders headers = new HttpHeaders(); + headers.add("Cookie", tokenToUse); + try { - ResponseEntity exchange = restTemplate.exchange(new RequestEntity<>(headers, HttpMethod.GET, URI.create(queryUrl + "api/account")), CentralAuthUserInfo.class); - logger.info("Made remote call to get user details for {} HTTP {}", username, exchange.getStatusCodeValue()); + ResponseEntity exchange = restTemplate.exchange( + new RequestEntity<>(headers, HttpMethod.GET, URI.create(queryUrl + "api/account")), + CentralAuthUserInfo.class + ); + logger.info("Made remote call to get user details for '{}' HTTP {}", username, exchange.getStatusCode()); return exchange.getBody(); + } catch (HttpClientErrorException e) { - throw new IOException("Unable to recover user account details for " + username + " received: " + e.getRawStatusCode()); + if (e.getStatusCode() == HttpStatus.UNAUTHORIZED || e.getStatusCode() == HttpStatus.FORBIDDEN) { + cookieTokenStore.remove(username); + logger.warn("Removed stored token for user '{}' due to HTTP {}", username,e.getStatusCode()); + } + throw new IOException("Unable to recover user account details for " + username + ". Received HTTP: " + e.getStatusCode()); } + } } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/service/AtomEntryImpl.java b/src/main/java/ca/intelliware/ihtsdo/mlds/service/AtomEntryImpl.java index f32a8f3f4..1fb611c3f 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/service/AtomEntryImpl.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/service/AtomEntryImpl.java @@ -213,6 +213,33 @@ public void setFeedBaseUrl(String feedBaseUrl) { private Map fileIdToFileHash = new HashMap<>(); + private static final Map EXTENSION_TO_MIME = Map.ofEntries( + Map.entry("xls", "application/vnd.ms-excel"), + Map.entry("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + Map.entry("doc", "application/msword"), + Map.entry("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), + Map.entry("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"), + Map.entry("xml", "application/xml"), + Map.entry("ods", "application/vnd.oasis.opendocument.spreadsheet"), + Map.entry("csv", "text/csv"), + Map.entry("pdf", "application/pdf"), + Map.entry("php", "application/x-httpd-php"), + Map.entry("jpg", "image/jpeg"), + Map.entry("png", "image/png"), + Map.entry("gif", "image/gif"), + Map.entry("bmp", "image/bmp"), + Map.entry("txt", "text/plain"), + Map.entry("js", "application/javascript"), + Map.entry("swf", "application/x-shockwave-flash"), + Map.entry("mp3", "audio/mpeg"), + Map.entry("zip", "application/zip"), + Map.entry("rar", "application/vnd.rar"), + Map.entry("tar", "application/x-tar"), + Map.entry("html", "text/html"), + Map.entry("htm", "text/html") + ); + + public void addLink(String versionId, String fileId, boolean primaryFile, String downloadUrl, String md5Hash, String fileSize) { versionLinks.computeIfAbsent(versionId, k -> new ArrayList<>()).add(fileId); @@ -225,82 +252,159 @@ public void addLink(String versionId, String fileId, boolean primaryFile, String public String toXml() { StringBuilder entryXml = new StringBuilder(); entryXml.append(" \n"); + entryXml.append(" ").append(title).append("\n"); + // for links + entryXml.append(buildLinksXml()); + // for category + entryXml.append(buildCategoryXml(packageType)); + // for author + entryXml.append(buildAuthorXml(memberOrgName, memberOrgURL, contactEmail)); + + entryXml.append(" urn:uuid:").append(id).append("\n"); + + if (!"null".equals(copyrights) && !copyrights.isEmpty()) { + entryXml.append(" ").append(copyrights).append("\n"); + } + + entryXml.append(" ").append(updated).append("\n"); + + if (!"null".equals(publishedAt) && !publishedAt.isEmpty()) { + entryXml.append(" ").append(publishedAt).append("T00:00:00Z\n"); + } + + if (!"null".equals(summary) && !summary.isEmpty()) { + entryXml.append(" ").append(summary).append("\n"); + } + + // for content xml + entryXml.append(buildContentItemXml(releasePackageURI, versionURI, versionDependentURI, versionDependentDerivativeURI)); + + entryXml.append(" \n"); + + return entryXml.toString(); + } + + private String buildLinksXml() { + StringBuilder linksXml = new StringBuilder(); for (Map.Entry> entry : versionLinks.entrySet()) { - String versionId = entry.getKey(); + String releaseVersionId = entry.getKey(); List links = entry.getValue(); - for (String fileId : links) { - String downloadUrl = feedBaseUrl + "api/releasePackages/" + packageId + "/releaseVersions/" + versionId + "/releaseFiles/" + fileId + "/download"; - boolean checkPrimaryFile = fileIdToPrimaryFileMap.get(fileId); - String fileUrl = fileIdToDownloadUrlMap.get(fileId); - String checkFileSize = fileIdToFileSizeMap.get(fileId); - String fileHash = fileIdToFileHash.get(fileId); + for (String releaseFileId : links) { + String fileDownloadUrl = feedBaseUrl + "api/releasePackages/" + packageId + + "/releaseVersions/" + releaseVersionId + "/releaseFiles/" + releaseFileId + "/download"; + boolean checkPrimaryFile = fileIdToPrimaryFileMap.get(releaseFileId); + String fileUrl = fileIdToDownloadUrlMap.get(releaseFileId); + String checkFileSize = fileIdToFileSizeMap.get(releaseFileId); + String fileHash = fileIdToFileHash.get(releaseFileId); String fileExtension = getFileExtension(fileUrl); + String mimeType = getMimeTypeFromExtension(fileExtension); - if (!"null".equals(checkFileSize) && !"null".equals(fileHash) && !checkFileSize.isEmpty() && !fileHash.isEmpty()) { - if (checkPrimaryFile) { - entryXml.append(" \n"); - } else { - entryXml.append(" \n"); - } - } - - else { - if(checkPrimaryFile){ - entryXml.append(" \n"); - } - else{ - entryXml.append(" \n"); - } - } + appendLinkXml(linksXml, fileDownloadUrl, checkPrimaryFile, mimeType, checkFileSize, fileHash); } } + return linksXml.toString(); + } - //for category - if(packageType.equals("SCT_RF2_SNAPSHOT")) { - entryXml.append(" \n"); - } - if(packageType.equals("SCT_RF2_FULL")) { - entryXml.append(" \n"); + private void appendLinkXml(StringBuilder xml, String downloadUrl, boolean isPrimaryFile, String mimeType, String fileSize, String fileHash) { + String rel = isPrimaryFile ? "alternate" : "related"; + xml.append(" \n"); + xml.append(" />\n"); + } + + private String buildAuthorXml(String memberOrgName, String memberOrgURL, String contactEmail) { + StringBuilder authorXml = new StringBuilder(); + authorXml.append(" \n"); + authorXml.append(" ").append(memberOrgName).append("\n"); + if (!"null".equals(memberOrgURL) && !memberOrgURL.isEmpty()) { + authorXml.append(" ").append(memberOrgURL).append("\n"); } - if(!packageType.equals("SCT_RF2_SNAPSHOT") && !packageType.equals("SCT_RF2_FULL") && !packageType.equals("SCT_RF2_ALL")) { - entryXml.append(" \n"); + if (!"null".equals(contactEmail) && !contactEmail.isEmpty()) { + authorXml.append(" ").append(contactEmail).append("\n"); } + authorXml.append(" \n"); + return authorXml.toString(); + } + + private String buildCategoryXml(String packageType) { + StringBuilder categoryXml = new StringBuilder(); + String scheme = "http://ns.electronichealth.net.au/ncts/syndication/asf/scheme/1.0.0"; + String label = switch (packageType) { + case "SCT_RF2_SNAPSHOT" -> "SNOMED CT RF2 Snapshot"; + case "SCT_RF2_FULL" -> "SNOMED CT RF2 Full"; + case "SCT_RF2_ALL" -> "SNOMED CT RF2 All"; + default -> { + packageType = "OTHER"; + yield "Other Package"; + } + }; - entryXml.append(" \n"); - entryXml.append(" ").append(memberOrgName).append("\n"); - entryXml.append(" ").append(memberOrgURL).append("\n"); - entryXml.append(" ").append(contactEmail).append("\n"); - entryXml.append(" \n"); - entryXml.append(" urn:uuid:").append(id).append("\n"); - entryXml.append(" ").append(copyrights).append("\n"); - entryXml.append(" ").append(updated).append("\n"); - entryXml.append(" ").append(publishedAt).append("T00:00:00Z\n"); - entryXml.append(" ").append(summary).append("\n"); - entryXml.append(" ").append(releasePackageURI).append("\n"); + categoryXml.append(" \n"); + return categoryXml.toString(); + } + + + private String buildContentItemXml(String releasePackageURI, String versionURI, String versionDependentURI, String versionDependentDerivativeURI) { + StringBuilder contentXml = new StringBuilder(); + + contentXml.append(" ").append(releasePackageURI).append("\n"); + contentXml.append(" ").append(versionURI).append("\n"); - entryXml.append(" ").append(versionURI).append("\n"); if(versionDependentURI != null && !versionDependentURI.isEmpty() && !Objects.equals(versionDependentURI, "null")){ - entryXml.append(" \n"); - entryXml.append(" ").append(versionDependentURI).append("\n"); + contentXml.append(" \n"); + contentXml.append(" ").append(versionDependentURI).append("\n"); if(versionDependentDerivativeURI != null && !versionDependentDerivativeURI.isEmpty() && !Objects.equals(versionDependentDerivativeURI, "null")){ - entryXml.append(" ").append(versionDependentDerivativeURI).append("\n"); + contentXml.append(" ").append(versionDependentDerivativeURI).append("\n"); } - entryXml.append(" \n"); + contentXml.append(" \n"); } - entryXml.append(" \n"); - - return entryXml.toString(); + return contentXml.toString(); } private String getFileExtension(String fileUrl) { - String fileExtension = fileUrl.substring(fileUrl.lastIndexOf('.') + 1); - return fileExtension; + if (fileUrl == null || !fileUrl.contains(".")) { + return ""; + } + + // Remove query params or fragments if any + int queryIndex = fileUrl.indexOf('?'); + int hashIndex = fileUrl.indexOf('#'); + + int endIndex = fileUrl.length(); + if (queryIndex != -1 && hashIndex != -1) { + endIndex = Math.min(queryIndex, hashIndex); + } else if (queryIndex != -1) { + endIndex = queryIndex; + } else if (hashIndex != -1) { + endIndex = hashIndex; + } + + String cleanUrl = fileUrl.substring(0, endIndex); + int lastDotIndex = cleanUrl.lastIndexOf('.'); + if (lastDotIndex == -1 || lastDotIndex == cleanUrl.length() - 1) { + return ""; + } + + return cleanUrl.substring(lastDotIndex + 1); + } + + private String getMimeTypeFromExtension(String extension) { + if (extension == null || extension.isBlank()) { + return "application/octet-stream"; + } + return EXTENSION_TO_MIME.getOrDefault(extension.toLowerCase(), "application/octet-stream"); } + + } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageAccessService.java b/src/main/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageAccessService.java new file mode 100644 index 000000000..f2f48baa1 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageAccessService.java @@ -0,0 +1,406 @@ +package ca.intelliware.ihtsdo.mlds.service; + +import ca.intelliware.ihtsdo.mlds.domain.ReleasePackage; +import ca.intelliware.ihtsdo.mlds.domain.ReleasePackageConfig; +import ca.intelliware.ihtsdo.mlds.domain.ReleasePermissionType; +import ca.intelliware.ihtsdo.mlds.domain.User; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageAccessRepository; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageConfigRepository; +import ca.intelliware.ihtsdo.mlds.repository.UserRepository; +import ca.intelliware.ihtsdo.mlds.security.ihtsdo.CurrentSecurityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Transactional +public class ReleasePackageAccessService { + + private final Logger log = LoggerFactory.getLogger(ReleasePackageAccessService.class); + + private final ReleasePackageConfigRepository releasePackageConfigRepository; + private final ReleasePackageAccessRepository releasePackageAccessRepository; + private final UserRepository userRepository; + private final CurrentSecurityContext currentSecurityContext; + + public ReleasePackageAccessService(ReleasePackageConfigRepository releasePackageConfigRepository, + ReleasePackageAccessRepository releasePackageAccessRepository, + UserRepository userRepository, + CurrentSecurityContext currentSecurityContext) { + this.releasePackageConfigRepository = releasePackageConfigRepository; + this.releasePackageAccessRepository = releasePackageAccessRepository; + this.userRepository = userRepository; + this.currentSecurityContext = currentSecurityContext; + } + + private static final String PERMISSION_EVERYONE = "EVERYONE"; + private static final String PERMISSION_ADMIN_ONLY = "ADMIN_ONLY"; + private static final String PERMISSION_ADMIN_AND_STAFF = "ADMIN_AND_STAFF"; + private static final String PERMISSION_ADMIN_STAFF_AFFILIATES = "ADMIN_STAFF_AFFILIATES"; + + private static final String PERMISSION_NOT_SELECTED = "NOT_SELECTED"; + private static final String RELEASE_TYPE_ONLINE = "ONLINE"; + private static final String RELEASE_TYPE_ALPHA_BETA = "ALPHA/BETA"; + private static final String RELEASE_TYPE_OFFLINE = "OFFLINE"; + + + public Collection getAccessiblePackagesForStaff( + Collection allPackages, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages, + String staffMemberKey + ) { + Set result = new HashSet<>(); + if(currentSecurityContext.isStaff()) { + result = getStaffOwnedPackages(allPackages, staffMemberKey); + } + + List grantedPermissions = + releasePackageConfigRepository.findByReleasePermissionTypeNot(PERMISSION_NOT_SELECTED); + + List notGrantedPermissions = + releasePackageConfigRepository.findByReleasePermissionType(PERMISSION_NOT_SELECTED); + + // Handle granted permissions + for (ReleasePackageConfig config : grantedPermissions) { + String type = config.getReleaseType(); + String permission = config.getReleasePermissionType(); + + if ("ALL".equalsIgnoreCase(type)) { + return handleAllTypePermission(permission, allPackages, result); + } + + if (!PERMISSION_ADMIN_ONLY.equalsIgnoreCase(permission)) { + addByType(result, type, onlinePackages, alphaBetaPackages, offlinePackages); + } + } + + // Handle not granted permissions + for (ReleasePackageConfig config : notGrantedPermissions) { + String type = config.getReleaseType().toUpperCase(); + List filtered = getFilteredPackagesByType( + type, onlinePackages, alphaBetaPackages, offlinePackages + ); + result.addAll(filtered); + } + + return result; + } + + private Set getStaffOwnedPackages(Collection packages, String staffMemberKey) { + return packages.stream() + .filter(pkg -> pkg.getMember().getKey().equals(staffMemberKey)) + .collect(Collectors.toSet()); + } + + private Collection handleAllTypePermission( + String permissionType, + Collection allPackages, + Set staffOwned + ) { + if (PERMISSION_ADMIN_ONLY.equalsIgnoreCase(permissionType)) { + return new HashSet<>(staffOwned); + } + return allPackages; + } + + private void addByType( + Set result, + String type, + List online, + List alphaBeta, + List offline + ) { + switch (type.toUpperCase()) { + case RELEASE_TYPE_ONLINE -> result.addAll(online); + case RELEASE_TYPE_ALPHA_BETA -> result.addAll(alphaBeta); + case RELEASE_TYPE_OFFLINE -> result.addAll(offline); + default -> log.warn("Unknown release type encountered: {}", type); + } + } + + private List getFilteredPackagesByType( + String type, + List online, + List alphaBeta, + List offline + ) { + List source = switch (type) { + case RELEASE_TYPE_ONLINE -> online; + case RELEASE_TYPE_ALPHA_BETA -> alphaBeta; + case RELEASE_TYPE_OFFLINE -> offline; + default -> Collections.emptyList(); + }; + + return source.stream() + .filter(pkg -> pkg.getPermissionType() != ReleasePermissionType.ADMIN_ONLY && + pkg.getPermissionType() != ReleasePermissionType.NOT_SELECTED) + .toList(); + } + + private List getPackageListByType( + String type, + List online, + List alphaBeta, + List offline + ) { + return switch (type) { + case RELEASE_TYPE_ONLINE -> online; + case RELEASE_TYPE_ALPHA_BETA -> alphaBeta; + case RELEASE_TYPE_OFFLINE -> offline; + default -> Collections.emptyList(); + }; + } + + public Collection getAccessiblePackagesForUnauthenticatedUser( + Collection allPackages, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages + ) { + Set result = new HashSet<>(); + + List grantedPermissions = + releasePackageConfigRepository.findByReleasePermissionTypeNot(PERMISSION_NOT_SELECTED); + List notGrantedPermissions = + releasePackageConfigRepository.findByReleasePermissionType(PERMISSION_NOT_SELECTED); + + handleGrantedPermissionsForUnauthUser(grantedPermissions, result, allPackages, onlinePackages, alphaBetaPackages, offlinePackages); + handleNotGrantedPermissionsForUnauthUser(notGrantedPermissions, result, onlinePackages, alphaBetaPackages, offlinePackages); + + return result; + } + + private void handleGrantedPermissionsForUnauthUser( + List grantedPermissions, + Set result, + Collection allPackages, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages + ) { + for (ReleasePackageConfig config : grantedPermissions) { + String type = config.getReleaseType(); + String permission = config.getReleasePermissionType(); + + if ("ALL".equalsIgnoreCase(type)) { + if (isRestrictedPermission(permission)) { + return; + } + if (PERMISSION_EVERYONE.equalsIgnoreCase(permission)) { + result.addAll(allPackages); + return; + } + } else if (PERMISSION_EVERYONE.equalsIgnoreCase(permission)) { + addPackagesByType(result, type, onlinePackages, alphaBetaPackages, offlinePackages); + } + } + } + + private void handleNotGrantedPermissionsForUnauthUser( + List notGrantedPermissions, + Set result, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages + ) { + for (ReleasePackageConfig config : notGrantedPermissions) { + String type = config.getReleaseType().toUpperCase(); + List packages = getPackageListByType(type, onlinePackages, alphaBetaPackages, offlinePackages); + result.addAll(packages.stream() + .filter(pkg -> pkg.getPermissionType() == ReleasePermissionType.EVERYONE) + .toList()); + } + } + + + public Collection getAccessiblePackagesForUser( + Collection allPackages, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages, + String username + ) { + Set finalResult = new HashSet<>(); + User user = userRepository.findByLoginIgnoreCase(username); + long userId = user.getUserId(); + + List grantedPermissions = + releasePackageConfigRepository.findByReleasePermissionTypeNot(PERMISSION_NOT_SELECTED); + List notGrantedPermissions = + releasePackageConfigRepository.findByReleasePermissionType(PERMISSION_NOT_SELECTED); + + processGrantedPermissionsForUser(grantedPermissions, userId, allPackages, finalResult, onlinePackages, alphaBetaPackages, offlinePackages); + processNotGrantedPermissionsForUser(notGrantedPermissions, userId, finalResult, onlinePackages, alphaBetaPackages, offlinePackages); + + return finalResult; + } + + private void processGrantedPermissionsForUser( + List configs, + long userId, + Collection allPackages, + Set result, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages + ) { + for (ReleasePackageConfig config : configs) { + String type = config.getReleaseType().toUpperCase(); + String permission = config.getReleasePermissionType(); + + if ("ALL".equals(type)) { + if (isAdminOnlyOrStaff(permission)) return; + if (isEveryoneOrAffiliates(permission) || config.getUserList().contains(String.valueOf(userId))) { + result.addAll(allPackages); + return; + } + } else if (!isAdminOnlyOrStaff(permission) && + (isEveryoneOrAffiliates(permission) || config.getUserList().contains(String.valueOf(userId)))) { + addPackagesByType(result, type, onlinePackages, alphaBetaPackages, offlinePackages); + } + } + } + + private void processNotGrantedPermissionsForUser( + List configs, + long userId, + Set result, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages + ) { + for (ReleasePackageConfig config : configs) { + String type = config.getReleaseType().toUpperCase(); + List packages = getPackageListByType(type, onlinePackages, alphaBetaPackages, offlinePackages); + + result.addAll(packages.stream() + .filter(pkg -> pkg.getPermissionType() == ReleasePermissionType.EVERYONE || + pkg.getPermissionType() == ReleasePermissionType.ADMIN_STAFF_AFFILIATES) + .toList()); + + packages.stream() + .filter(pkg -> pkg.getPermissionType() == ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS) + .filter(pkg -> releasePackageAccessRepository.existsByReleasePackageIdAndUserId(pkg.getReleasePackageId(), userId)) + .forEach(result::add); + } + } + + private void addPackagesByType( + Set target, + String type, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages + ) { + target.addAll(getPackageListByType(type, onlinePackages, alphaBetaPackages, offlinePackages)); + } + + private boolean isRestrictedPermission(String permission) { + return List.of(PERMISSION_ADMIN_ONLY, PERMISSION_ADMIN_AND_STAFF, + PERMISSION_ADMIN_STAFF_AFFILIATES, "ADMIN_STAFF_SELECTED_USERS") + .contains(permission.toUpperCase()); + } + + private boolean isAdminOnlyOrStaff(String permission) { + return PERMISSION_ADMIN_ONLY.equalsIgnoreCase(permission) || + PERMISSION_ADMIN_AND_STAFF.equalsIgnoreCase(permission); + } + + private boolean isEveryoneOrAffiliates(String permission) { + return PERMISSION_EVERYONE.equalsIgnoreCase(permission) || + PERMISSION_ADMIN_STAFF_AFFILIATES.equalsIgnoreCase(permission); + } + + + // for each release version access + + public boolean isStaffOwner(ReleasePackage rp) { + return currentSecurityContext.isStaff() && + currentSecurityContext.getStaffMemberKey().equals(rp.getMember().getKey()); + } + + public boolean isAdminOnly(ReleasePackage rp, ReleasePackageConfig config, ReleasePackageConfig master) { + + if (rp.getPermissionType() == ReleasePermissionType.ADMIN_ONLY) { + return true; + } + + if (rp.getPermissionType() == ReleasePermissionType.NOT_SELECTED) { + String masterPermission = master.getReleasePermissionType(); + String configPermission = config.getReleasePermissionType(); + + if (ReleasePermissionType.ADMIN_ONLY.toString().equals(masterPermission) + || ReleasePermissionType.ADMIN_ONLY.toString().equals(configPermission)) { + return true; + } + + return ReleasePermissionType.NOT_SELECTED.toString().equals(masterPermission) + && ReleasePermissionType.NOT_SELECTED.toString().equals(configPermission); + } + + return false; + } + + + public ResponseEntity handlePublicAccess(ReleasePackage rp, ReleasePackageConfig config, ReleasePackageConfig master) { + if (rp.getPermissionType() == ReleasePermissionType.EVERYONE) return ResponseEntity.ok(rp); + + if (rp.getPermissionType() == ReleasePermissionType.NOT_SELECTED && + (ReleasePermissionType.EVERYONE.toString().equals(master.getReleasePermissionType()) || + ReleasePermissionType.EVERYONE.toString().equals(config.getReleasePermissionType()))) { + return ResponseEntity.ok(rp); + } + + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + + public ResponseEntity handleUserAccess(ReleasePackage rp, ReleasePackageConfig config, ReleasePackageConfig master, long rpId) { + long userId = userRepository.findByLoginIgnoreCase(currentSecurityContext.getCurrentUserName()).getUserId(); + ReleasePermissionType type = rp.getPermissionType(); + + if (type == ReleasePermissionType.ADMIN_ONLY || type == ReleasePermissionType.ADMIN_AND_STAFF) + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + + if (type == ReleasePermissionType.EVERYONE || type == ReleasePermissionType.ADMIN_STAFF_AFFILIATES) + return ResponseEntity.ok(rp); + + if (type == ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS && + releasePackageAccessRepository.existsByReleasePackageIdAndUserId(rpId, userId)) { + return ResponseEntity.ok(rp); + } + + if (type == ReleasePermissionType.NOT_SELECTED) { + if (isEveryoneOrAffiliate(master) || isEveryoneOrAffiliate(config)) { + return ResponseEntity.ok(rp); + } + + if (isSelectedUser(master, userId) || isSelectedUser(config, userId)) { + return ResponseEntity.ok(rp); + } + } + + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + + private boolean isEveryoneOrAffiliate(ReleasePackageConfig cfg) { + String perm = cfg.getReleasePermissionType(); + return ReleasePermissionType.EVERYONE.toString().equals(perm) + || ReleasePermissionType.ADMIN_STAFF_AFFILIATES.toString().equals(perm); + } + + private boolean isSelectedUser(ReleasePackageConfig cfg, long userId) { + return ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS.toString().equals(cfg.getReleasePermissionType()) + && cfg.getUserList().contains(String.valueOf(userId)); + } + + +} + diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageService.java b/src/main/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageService.java new file mode 100644 index 000000000..98c8e23e6 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageService.java @@ -0,0 +1,352 @@ +package ca.intelliware.ihtsdo.mlds.service; + +import ca.intelliware.ihtsdo.mlds.domain.*; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageAccessRepository; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageConfigRepository; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageRepository; +import ca.intelliware.ihtsdo.mlds.repository.UserRepository; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +@Service +@Transactional +public class ReleasePackageService { + + private final ReleasePackageRepository releasePackageRepository; + private final ReleasePackageAccessRepository releasePackageAccessRepository; + private final ReleasePackageConfigRepository releasePackageConfigRepository; + private final UserRepository userRepository; + private final ObjectMapper objectMapper; + + private static final String RELEASE_TYPE_ONLINE = "online"; + private static final String RELEASE_TYPE_ALPHA_BETA = "alpha/beta"; + private static final String RELEASE_TYPE_OFFLINE = "offline"; + private static final String USER_ACCESS_REVOKED_SUCCESS = "User access revoked successfully."; + + + public ReleasePackageService(ReleasePackageRepository releasePackageRepository, + ReleasePackageAccessRepository releasePackageAccessRepository, + ReleasePackageConfigRepository releasePackageConfigRepository, + UserRepository userRepository, + ObjectMapper objectMapper) { + this.releasePackageRepository = releasePackageRepository; + this.releasePackageAccessRepository = releasePackageAccessRepository; + this.releasePackageConfigRepository = releasePackageConfigRepository; + this.userRepository = userRepository; + this.objectMapper = objectMapper; + } + + + public void updateReleasePackagesPermission(Map request) { + List releasePackages = (List) request.get("releases"); + String releasePackageType = (String) request.get("releasePackageType"); + List users = (List) request.get("users"); + + List packages = releasePackageRepository.findAllByReleasePackageIdIn(releasePackages); + + Set affectedTypes = new HashSet<>(); + List accessList = new ArrayList<>(); + + for (ReleasePackage pkg : packages) { + updatePermissionType(pkg, releasePackageType); + + if (requiresUserAccess(pkg, users)) { + accessList.addAll(createAccessList(pkg, users)); + } + + if (!pkg.getReleaseVersions().isEmpty()) { + Set types = extractAffectedTypes(pkg); + affectedTypes.addAll(types); + } + } + + if (!accessList.isEmpty()) { + releasePackageAccessRepository.saveAll(accessList); + } + + releasePackageRepository.saveAll(packages); + + revokePermissions(affectedTypes); + } + + private void updatePermissionType(ReleasePackage pkg, String type) { + pkg.setPermissionType(ReleasePermissionType.valueOf(type)); + } + + private boolean requiresUserAccess(ReleasePackage pkg, List users) { + return pkg.getPermissionType() == ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS && users != null; + } + + private List createAccessList(ReleasePackage pkg, List users) { + List list = new ArrayList<>(); + for (String user : users) { + ReleasePackageAccess access = new ReleasePackageAccess(); + access.setReleasePackageId(pkg.getReleasePackageId()); + access.setUserId(Long.parseLong(user)); + list.add(access); + } + return list; + } + + private Set extractAffectedTypes(ReleasePackage pkg) { + Map typeCount = new HashMap<>(); + for (ReleaseVersion version : pkg.getReleaseVersions()) { + if (!version.isArchive()) { + String type = version.getReleaseType(); + typeCount.put(type, typeCount.getOrDefault(type, 0) + 1); + } + } + + List priority = Arrays.asList(RELEASE_TYPE_ONLINE, RELEASE_TYPE_ALPHA_BETA, RELEASE_TYPE_OFFLINE); + for (String type : priority) { + if (typeCount.containsKey(type)) { + return Set.of(type.toUpperCase()); + } + } + + return Collections.emptySet(); + } + + private void revokePermissions(Set releaseTypes) { + for (String type : releaseTypes) { + deactivateConfig(type); + } + deactivateConfig("ALL"); + } + + + // updateReleaseMasterConfig + + public void updateReleaseMasterConfig(Map request) { + String releaseType = (String) request.get("releaseType"); + String releasePackage = (String) request.get("releasePackage"); + String releasePermissionType = (String) request.get("releasePermissionType"); + List users = (List) request.get("users"); + + String userListJson = serializeUserList(users); + + ReleasePackageConfig config = releasePackageConfigRepository.findByReleaseType(releaseType); + if (config == null) { + throw new IllegalStateException("Release type config not found: " + releaseType); + } + + config.setReleasePackageAccess(releasePackage); + config.setReleasePermissionType(releasePermissionType); + config.setActive(true); + config.setUserList(userListJson); + releasePackageConfigRepository.save(config); + + classifyAndRevokePackages(releaseType); + } + + private String serializeUserList(List users) { + try { + return objectMapper.writeValueAsString(users); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize user list", e); + } + } + + private void classifyAndRevokePackages(String releaseType) { + Collection releasePackages = releasePackageRepository.findAll(); + + List onlinePackages = new ArrayList<>(); + List alphaBetaPackages = new ArrayList<>(); + List offlinePackages = new ArrayList<>(); + + for (ReleasePackage pkg : releasePackages) { + classifyPackage(pkg, onlinePackages, alphaBetaPackages, offlinePackages); + } + + switch (releaseType.toLowerCase()) { + case RELEASE_TYPE_ONLINE -> revokeAndUpdate(onlinePackages); + case RELEASE_TYPE_ALPHA_BETA -> revokeAndUpdate(alphaBetaPackages); + case RELEASE_TYPE_OFFLINE -> revokeAndUpdate(offlinePackages); + case "all" -> revokeAllTypes(); + default -> throw new IllegalArgumentException("Unsupported release type: " + releaseType); + } + } + + public void classifyPackage( + ReleasePackage pkg, + List onlinePackages, + List alphaBetaPackages, + List offlinePackages + ) { + boolean hasOnline = false; + boolean hasAlphaBeta = false; + + for (ReleaseVersion version : pkg.getReleaseVersions()) { + if (!version.isArchive()) { + String type = version.getReleaseType().toLowerCase(); + + if (type.equals(RELEASE_TYPE_ONLINE)) { + hasOnline = true; + break; + } + + if (type.equals(RELEASE_TYPE_ALPHA_BETA)) { + hasAlphaBeta = true; + } + } + } + + + if (hasOnline) { + onlinePackages.add(pkg); + } else if (hasAlphaBeta) { + alphaBetaPackages.add(pkg); + } else { + offlinePackages.add(pkg); + } + } + + + private void revokeAndUpdate(List packages) { + List ids = packages.stream() + .map(ReleasePackage::getReleasePackageId) + .toList(); + + releasePackageRepository.updatePermissionTypeForPackages(ReleasePermissionType.NOT_SELECTED, ids); + releasePackageAccessRepository.deleteReleasePackageAccessByPackageIds(ids); + deactivateConfig("ALL"); + } + + private void revokeAllTypes() { + releasePackageRepository.updatePermissionTypeForAllPackages(ReleasePermissionType.NOT_SELECTED); + releasePackageAccessRepository.deleteAll(); + + List configs = releasePackageConfigRepository.findAll(); + for (ReleasePackageConfig config : configs) { + if (!"ALL".equalsIgnoreCase(config.getReleaseType())) { + config.setReleasePermissionType(ReleasePermissionType.NOT_SELECTED.name()); + config.setActive(false); + config.setUserList("[]"); + releasePackageConfigRepository.save(config); + } + } + } + + private void deactivateConfig(String type) { + ReleasePackageConfig config = releasePackageConfigRepository.findByReleaseType(type); + if (config != null) { + config.setReleasePermissionType(ReleasePermissionType.NOT_SELECTED.toString()); + config.setActive(false); + config.setUserList("[]"); + releasePackageConfigRepository.save(config); + } + } + + + // user access revoking by clicking from ui logics + + + public String revokeIndividualUserAccess(String releaseId, String requestUser) throws JsonProcessingException { + List masterConfigIds = Arrays.asList("ONLINE", "ALPHA/BETA", "OFFLINE", "ALL"); + + User user = userRepository.findByLoginIgnoreCase(requestUser); + long userId = user.getUserId(); + + if (masterConfigIds.contains(releaseId)) { + ReleasePackageConfig masterPermission = releasePackageConfigRepository.findByReleaseType(releaseId); + List userList = objectMapper.readValue(masterPermission.getUserList(), new TypeReference>() {}); + + if (userList.remove(userId)) { + masterPermission.setUserList(objectMapper.writeValueAsString(userList)); + releasePackageConfigRepository.save(masterPermission); + return USER_ACCESS_REVOKED_SUCCESS; + } + return "User ID not found in the list."; + } + + releasePackageAccessRepository.deleteReleasePackageAccessByPackageIdAndUserId(Long.valueOf(releaseId), userId); + return USER_ACCESS_REVOKED_SUCCESS; + } + + public String revokeAllReleaseAccess(String releaseId) { + List masterConfigIds = Arrays.asList("ONLINE", "ALPHA/BETA", "OFFLINE", "ALL"); + + if (masterConfigIds.contains(releaseId)) { + ReleasePackageConfig config = releasePackageConfigRepository.findByReleaseType(releaseId); + config.setUserList("[]"); + config.setReleasePermissionType(String.valueOf(ReleasePermissionType.NOT_SELECTED)); + config.setActive(false); + releasePackageConfigRepository.save(config); + } else { + Optional config = releasePackageRepository.findById(Long.valueOf(releaseId)); + if (config.isPresent()) { + ReleasePackage releasePackage = config.get(); + + if (releasePackage.getPermissionType().equals(ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS)) { + releasePackageAccessRepository.deleteReleasePackageAccessByPackageId(Long.valueOf(releaseId)); + } + + releasePackage.setPermissionType(ReleasePermissionType.NOT_SELECTED); + releasePackageRepository.save(releasePackage); + } else { + return "Release package not found."; + } + } + + return USER_ACCESS_REVOKED_SUCCESS; + } + + // check visiblity + + public PermissionVisibilityResponse getVisibilityDetails(ReleasePackage releasePackage) { + String packageType = categorizePackage(releasePackage.getReleaseVersions()); + ReleasePackageConfig releaseTypePermission = releasePackageConfigRepository.findByReleaseTypeIgnoreCase(packageType); + + boolean isMasterPermission = true; + String permissionType = ""; + + if (releaseTypePermission != null && Boolean.FALSE.equals(releaseTypePermission.getActive())) { + isMasterPermission = false; + permissionType = releasePackage.getPermissionType().toString(); + } else { + permissionType = releaseTypePermission != null ? releaseTypePermission.getReleasePermissionType() : ""; + } + + return new PermissionVisibilityResponse(isMasterPermission, permissionType, packageType); + } + + public ReleasePackageConfig getMasterPermission(String releaseType) { + return releasePackageConfigRepository.findByReleaseType(releaseType); + } + + public String categorizePackage(Set releaseVersions) { + boolean hasOnline = false; + boolean hasAlphaOrBeta = false; + + for (ReleaseVersion version : releaseVersions) { + if (!version.isArchive()) { + String releaseType = version.getReleaseType().toLowerCase(); + + if (RELEASE_TYPE_ONLINE.equals(releaseType)) { + hasOnline = true; + break; // No need to continue if we've already found an online version + } + + if (RELEASE_TYPE_ALPHA_BETA.equals(releaseType)) { + hasAlphaOrBeta = true; + } + } + } + + if (hasOnline) { + return RELEASE_TYPE_ONLINE; + } + else if (hasAlphaOrBeta) { + return RELEASE_TYPE_ALPHA_BETA; + } + else { + return RELEASE_TYPE_OFFLINE; + } + } + +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/service/UserService.java b/src/main/java/ca/intelliware/ihtsdo/mlds/service/UserService.java index f92361cbf..681ffefea 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/service/UserService.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/service/UserService.java @@ -1,13 +1,10 @@ package ca.intelliware.ihtsdo.mlds.service; -import ca.intelliware.ihtsdo.mlds.domain.Authority; -import ca.intelliware.ihtsdo.mlds.domain.PersistentToken; -import ca.intelliware.ihtsdo.mlds.domain.User; -import ca.intelliware.ihtsdo.mlds.repository.AuthorityRepository; -import ca.intelliware.ihtsdo.mlds.repository.PersistentTokenRepository; -import ca.intelliware.ihtsdo.mlds.repository.UserRepository; +import ca.intelliware.ihtsdo.mlds.domain.*; +import ca.intelliware.ihtsdo.mlds.repository.*; import ca.intelliware.ihtsdo.mlds.security.SecurityUtils; import ca.intelliware.ihtsdo.mlds.service.util.RandomUtil; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.AffiliateDetailsResponseDTO; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,11 +15,11 @@ import org.springframework.transaction.annotation.Transactional; +import java.time.Instant; import java.time.LocalDate; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.*; /** * Service class for managing users. @@ -44,10 +41,29 @@ public class UserService { @Autowired private AuthorityRepository authorityRepository; - + + + private MemberRepository memberRepository; + @Resource AutologinService autologinService; + private AffiliateDetailsRepository affiliateDetailsRepository; + + private AffiliateRepository affiliateRepository; + + ApplicationRepository applicationRepository; + + private CommercialUsageRepository commercialUsageRepository; + + public UserService(AffiliateDetailsRepository affiliateDetailsRepository, AffiliateRepository affiliateRepository, ApplicationRepository applicationRepository, MemberRepository memberRepository, CommercialUsageRepository commercialUsageRepository) { + this.affiliateDetailsRepository = affiliateDetailsRepository; + this.affiliateRepository = affiliateRepository; + this.applicationRepository = applicationRepository; + this.memberRepository = memberRepository; + this.commercialUsageRepository = commercialUsageRepository; + } + public User activateRegistration(String key) { log.debug("Activating user for activation key {}", key); User user = userRepository.getUserByActivationKey(key); @@ -58,10 +74,10 @@ public User activateRegistration(String key) { // MLDS-234 Rory requested that activation keys not expire // user.setActivationKey(null); autologinService.loginUser(user); - + log.debug("Activated user: {}", user); } - + return user; } @@ -104,17 +120,17 @@ public void changePassword(String password) { changePassword(currentUser, password); } - protected void changePassword(User user, String password) { - String encryptedPassword = passwordEncoder.encode(password); + protected void changePassword(User user, String password) { + String encryptedPassword = passwordEncoder.encode(password); user.setPassword(encryptedPassword); log.debug("Changed password for User: {}", user); - } + } @Transactional(readOnly = true) public User getUserWithAuthorities() { User currentUser = userRepository.findByLoginIgnoreCase(SecurityUtils.getCurrentLogin()); if (currentUser != null) { - currentUser.getAuthorities().size(); // eagerly load the association + currentUser.getAuthorities().size(); // eagerly load the association } return currentUser; } @@ -153,4 +169,330 @@ public void removeNotActivatedUsers() { userRepository.delete(user); } } + + public AffiliateDetailsResponseDTO getAffiliateDetails(String email) { + // Fetch user details + User user = userRepository.findByLoginIgnoreCaseSafe(email); + // Fetch affiliates by creator email + List affiliates = affiliateRepository.findByCreatorIgnoreCase(email); + // Return an empty DTO if no affiliates are found + if (affiliates.isEmpty()) { + return new AffiliateDetailsResponseDTO(user, null, Collections.emptyList()); + } + // Get first affiliate's details safely + Affiliate firstAffiliate = affiliates.get(0); + return new AffiliateDetailsResponseDTO(user, firstAffiliate.getAffiliateDetails(), affiliates); + } + + + @Transactional + public void updatePrimaryEmail(String login, String updatedEmail) { + User user = userRepository.findByLoginIgnoreCase(login); + if (user == null) { + throw new NoSuchElementException("User not found for login: " + login); + } + // Check if the updated email already exists in the system + User existingUser = userRepository.findByLoginIgnoreCase(updatedEmail); + if (existingUser != null) { + handleExistingUserConflict(existingUser, updatedEmail); + } + // Update the current user's email + updateUserEmail(user, updatedEmail); + // Update associated records + updateRelatedEntities(login, updatedEmail); + } + + /** + * Handles the scenario where the updated email already exists in the system. + */ + private void handleExistingUserConflict(User existingUser, String updatedEmail) { + String modifiedEmail = addOldToEmail(updatedEmail); + updateAffiliates(updatedEmail, modifiedEmail, true); // Deactivate affiliates + updateAffiliateDetails(updatedEmail, modifiedEmail); + updateApplications(updatedEmail, modifiedEmail); + // Deactivate and rename existing user + existingUser.setLogin(modifiedEmail); + existingUser.setEmail(modifiedEmail); + existingUser.setActivated(false); + userRepository.save(existingUser); + } + + /** + * Updates the email of the given user. + */ + private void updateUserEmail(User user, String updatedEmail) { + user.setLogin(updatedEmail); + user.setEmail(updatedEmail); + userRepository.save(user); + } + + /** + * Updates all related entities (Affiliates, Applications, and AffiliateDetails). + */ + private void updateRelatedEntities(String oldEmail, String newEmail) { + updateApplications(oldEmail, newEmail); + updateAffiliates(oldEmail, newEmail, false); + updateAffiliateDetails(oldEmail, newEmail); + } + + /** + * Updates the email field in the Application table. + */ + private void updateApplications(String oldEmail, String newEmail) { + List applications = applicationRepository.findByUsernameIgnoreCase(oldEmail); + if (!applications.isEmpty()) { + applications.forEach(app -> app.setUsername(newEmail)); + applicationRepository.saveAll(applications); + } + } + + /** + * Updates the creator field in the Affiliate table. + */ + private void updateAffiliates(String oldEmail, String newEmail, boolean deactivate) { + List affiliates = affiliateRepository.findByCreatorIgnoreCase(oldEmail); + if (!affiliates.isEmpty()) { + affiliates.forEach(affiliate -> { + affiliate.setCreator(newEmail); + if (deactivate) { + affiliate.setReasonForDeactivation(ReasonForDeactivation.PRIMARYCONTACTEMAIL); + affiliate.setLastProcessed(Instant.now()); + affiliate.setDeactivated(true); + } + }); + affiliateRepository.saveAll(affiliates); + } + } + + /** + * Updates the email field in the AffiliateDetails table. + */ + private void updateAffiliateDetails(String oldEmail, String newEmail) { + List affiliateDetailsList = affiliateDetailsRepository.getAllAffiliateDetailsByEmail(oldEmail); + if (!affiliateDetailsList.isEmpty()) { + affiliateDetailsList.forEach(details -> details.setEmail(newEmail)); + affiliateDetailsRepository.saveAll(affiliateDetailsList); + } + } + + /** + * Utility method to modify an email (adds "old" before "@"). + */ + private String addOldToEmail(String email) { + String[] parts = email.split("@"); + if (parts.length == 2) { + return parts[0] + "old@" + parts[1]; + } + throw new IllegalArgumentException("Invalid email format: " + email); + } + + @Scheduled(cron = "${scheduler.remove-pending-application.cron}") + public void removePendingApplication() { + Logger logger = LoggerFactory.getLogger(getClass()); + + List applications = applicationRepository.getAllApplication(); + logger.info("Total applications retrieved: {}", applications.size()); + + List filteredAffiliateIds = applications.stream() + .map(this::evaluateApplication) + .filter(Objects::nonNull) + .toList(); + + logger.info("Total applications meeting the criteria: {}", filteredAffiliateIds.size()); + + if (!filteredAffiliateIds.isEmpty()) { + applicationRepository.updateLastProcessed(filteredAffiliateIds, Instant.now()); + deactivateAffiliates(filteredAffiliateIds); + } + } + + private Long evaluateApplication(Application application) { + Logger logger = LoggerFactory.getLogger(getClass()); + + Member member = getMemberById(application.getMember().getMemberId()); + if (member == null) { + logger.warn("Member not found for application ID: {}", application.getApplicationId()); + return null; + } + + int pendingApplication = member.getPendingApplication(); + if (pendingApplication == 0) { + logger.info("Skipping application ID {}: PendingApplication is 0", application.getApplicationId()); + return null; + } + + LocalDate cutoffDate = getCutoffDate(pendingApplication); + LocalDate submittedAt = getLocalDate(application.getSubmittedAt()); + LocalDate completedAt = getLocalDate(application.getCompletedAt()); + + if (application.getAffiliate() == null) { + logger.warn("Affiliate is null for application ID: {}", application.getApplicationId()); + return null; + } + + boolean isOldCompleted = completedAt != null && completedAt.isBefore(cutoffDate); + boolean isOldSubmitted = submittedAt != null && submittedAt.isBefore(cutoffDate); + + return (isOldCompleted || isOldSubmitted) ? application.getAffiliate().getAffiliateId() : null; + } + + private LocalDate getLocalDate(Instant dateTime) { + return dateTime != null ? dateTime.atZone(ZoneId.systemDefault()).toLocalDate() : null; + } + + /** + * Scheduled process to deactivate affiliates whose invoices are in a pending state + * beyond the defined period for their respective member country. + *

+ * - Fetches all affiliates with pending invoices. + * - Retrieves the defined invoice pending period for the member. + * - Identifies affiliates whose invoice pending period has exceeded the cutoff date. + * - Performs a bulk deactivation for the identified affiliates. + *

+ * This ensures that affiliates who have not cleared their invoices within the allowed timeframe + * are deactivated automatically, maintaining compliance with membership policies. + */ + @Scheduled(cron = "${scheduler.remove-invoices-pending.cron}") + public void removeInvoicesPending() { + Logger logger = LoggerFactory.getLogger(getClass()); + + Member member = getMemberById(1L); + if (member.getInvoicesPending() == 0) { + logger.info("Skipping processing: PendingApplication is 0 for Member ID 1"); + return; + } + + LocalDate cutoffDate = getCutoffDate(member.getInvoicesPending()); + List affiliateIds = getFilteredAffiliateIds(cutoffDate); + updateLastProcessedForAffiliates(affiliateIds); + deactivateAffiliates(affiliateIds); + } + + private void updateLastProcessedForAffiliates(List affiliateIds) { + if (!affiliateIds.isEmpty()) { + affiliateRepository.updateLastProcessed(affiliateIds, Instant.now()); + } + } + + /*Get filtered affiliate IDs for Pending Incoices State For IHTSDO members*/ + private List getFilteredAffiliateIds(LocalDate cutoffDate) { + List affiliates = affiliateRepository.getIHTSDOPendingInvoices(); + + return affiliates.stream() + .filter(affiliate -> affiliate.getStandingState() == StandingState.PENDING_INVOICE + && affiliate.getCreated().atZone(ZoneId.systemDefault()).toLocalDate().isBefore(cutoffDate)) + .map(Affiliate::getAffiliateId) + .toList(); + + } + + /*Fetch member details using the Member Id*/ + private Member getMemberById(Long memberId) { + return memberRepository.findMemberById(memberId); + } + + /*Reusable Method to Compute cutoff date based on Standing State , Approval State, Usage Reports*/ + public LocalDate getCutoffDate(int invoicesPending) { + return LocalDate.now().minus(invoicesPending, ChronoUnit.DAYS); + } + + /* Reusable method for Bulk Deactivate the Affiliates if applicable */ + private void deactivateAffiliates(List affiliateid) { + Logger logger = LoggerFactory.getLogger(getClass()); + + if (!affiliateid.isEmpty()) { + // Fetch only active affiliates from the provided IDs + List activeAffiliateIds = affiliateRepository.findActiveAffiliateIds(new ArrayList<>(affiliateid)); + + if (!activeAffiliateIds.isEmpty()) { + int updatedCount = 0; + for (Long affiliateId : activeAffiliateIds) { + updatedCount = affiliateRepository.updateAffiliateStandingStateAndDeactivationReason(affiliateId, StandingState.DEREGISTERED, ReasonForDeactivation.AUTODEACTIVATION); + + } + logger.info("Total affiliates deactivated: {}", updatedCount); + } else { + logger.info("No active affiliates found for deactivation."); + } + } else { + logger.info("No affiliates provided for deactivation."); + } + } + + @Scheduled(cron = "${scheduler.remove-usage-reports.cron}") + public void removeUsageReports() { + Logger logger = LoggerFactory.getLogger(getClass()); + + List commercialUsages = commercialUsageRepository.findByState(); + + if (commercialUsages.isEmpty()) { + logger.info("No CommercialUsage records found with state 'NOT_SUBMITTED'."); + return; + } + + List affiliateIdsForDeactivation = commercialUsages.stream() + .map(this::processUsage) + .filter(Objects::nonNull) + .toList(); + + if (!affiliateIdsForDeactivation.isEmpty()) { + deactivateAffiliates(affiliateIdsForDeactivation); + logger.info("Deactivated {} affiliates", affiliateIdsForDeactivation.size()); + } else { + logger.info("No affiliates found for deactivation."); + } + } + + private Long processUsage(CommercialUsage usage) { + Logger logger = LoggerFactory.getLogger(getClass()); + + if (usage.getAffiliate() == null || usage.getAffiliate().getAffiliateId() == null) { + logger.warn("CommercialUsage ID {} has no valid affiliate.", usage.getCommercialUsageId()); + return null; + } + + Long affiliateId = usage.getAffiliate().getAffiliateId(); + Affiliate affiliate = affiliateRepository.findById(affiliateId).orElse(null); + + if (affiliate == null || affiliate.getHomeMember() == null) { + logger.warn("Affiliate or HomeMember is null for Affiliate ID {}", affiliateId); + return null; + } + + Member member = getMemberById(affiliate.getHomeMember().getMemberId()); + if (member == null) { + logger.warn("Member not found for ID: {}", affiliate.getHomeMember().getMemberId()); + return null; + } + + if (member.getUsageReports() == 0) { + logger.info("Skipping processing: Usage Reports is 0 for Member ID {}", member.getMemberId()); + return null; + } + + if (usage.getCreated() == null) { + logger.warn("Skipping CommercialUsage ID {}: Created date is null.", usage.getCommercialUsageId()); + return null; + } + + LocalDate createdDate = usage.getCreated().atZone(ZoneId.systemDefault()).toLocalDate(); + LocalDate cutoffDate = getCutoffDate(member.getUsageReports()); + + if (createdDate.isBefore(cutoffDate)) { + usage.setLastProcessed(Instant.now()); + commercialUsageRepository.save(usage); + return affiliateId; + } + + return null; + } + + } + + + + + + + diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/ClientLinkBuilder.java b/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/ClientLinkBuilder.java index 9206ba062..3266bcd77 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/ClientLinkBuilder.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/ClientLinkBuilder.java @@ -27,5 +27,10 @@ public String buildViewApplication(long applicationId) { public String buildViewReleasePackageLink(long releasePackageId) { return templateEvaluator.getUrlBase() + "#/viewReleases/viewRelease/"+releasePackageId; } + public String buildUnsubscribeLink(long affiliateId, String unsubscribeKey) { + // Build and return the unsubscribe URL + return templateEvaluator.getUrlBase() + "#/unsubscribenotification/" + affiliateId + "/" + unsubscribeKey; + } + } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/EmailVariables.java b/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/EmailVariables.java index e3cc3687f..ec309ff0b 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/EmailVariables.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/EmailVariables.java @@ -16,4 +16,5 @@ public class EmailVariables { static final String APPLICATION_MEMBER = "applicationMember"; static final String BLANK_TITLE = "blankTitle"; static final String BLANK_BODY = "blankBody"; -} \ No newline at end of file + static final String UNSUBSCRIBE_URL = "unsubscribeUrl"; +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/ReleasePackageUpdatedEmailSender.java b/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/ReleasePackageUpdatedEmailSender.java index 6ce9ff425..4f9c3076d 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/ReleasePackageUpdatedEmailSender.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/service/mail/ReleasePackageUpdatedEmailSender.java @@ -1,14 +1,16 @@ package ca.intelliware.ihtsdo.mlds.service.mail; +import ca.intelliware.ihtsdo.mlds.domain.Affiliate; import ca.intelliware.ihtsdo.mlds.domain.ReleasePackage; import ca.intelliware.ihtsdo.mlds.domain.ReleaseVersion; import ca.intelliware.ihtsdo.mlds.domain.User; +import ca.intelliware.ihtsdo.mlds.repository.AffiliateRepository; +import ca.intelliware.ihtsdo.mlds.repository.UserRepository; import com.google.common.collect.Maps; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; -import java.util.Locale; -import java.util.Map; +import java.util.*; @Service public class ReleasePackageUpdatedEmailSender { @@ -16,19 +18,65 @@ public class ReleasePackageUpdatedEmailSender { @Resource TemplateEvaluator templateEvaluator; @Resource ClientLinkBuilder clientLinkBuilder; - public void sendRelasePackageUpdatedEmail(User user, ReleasePackage releasePackage, ReleaseVersion releaseVersion) { - final Locale locale = Locale.forLanguageTag(user.getLangKey()); - Map variables = Maps.newHashMap(); - variables.put(EmailVariables.RELEASE_PACKAGE, releasePackage); - variables.put(EmailVariables.RELEASE_VERSION, releaseVersion); - variables.put(EmailVariables.USER, user); - variables.put(EmailVariables.MEMBERKEY, releasePackage.getMember().getKey()); - variables.put(EmailVariables.VIEW_RELEASE_PACKAGE_URL, clientLinkBuilder.buildViewReleasePackageLink(releasePackage.getReleasePackageId())); - variables.put(EmailVariables.VIEW_PACKAGES_URL, clientLinkBuilder.buildViewReleasesLink()); - String content = templateEvaluator.evaluateTemplate("releasePackageUpdatedEmail", locale, variables); - String subject = templateEvaluator.getTitleFor("releasePackageUpdated", locale); - - mailService.sendEmail(user.getEmail(), subject, content, false, true); - } + AffiliateRepository affiliateRepository; + UserRepository userRepository; + + public ReleasePackageUpdatedEmailSender(UserRepository userRepository, AffiliateRepository affiliateRepository) { + this.userRepository = userRepository; + this.affiliateRepository = affiliateRepository; + } + + public void sendRelasePackageUpdatedEmail(User user, ReleasePackage releasePackage, ReleaseVersion releaseVersion) { + final Locale locale = Locale.forLanguageTag(user.getLangKey()); + Map variables = Maps.newHashMap(); + + // Add existing variables + variables.put(EmailVariables.RELEASE_PACKAGE, releasePackage); + variables.put(EmailVariables.RELEASE_VERSION, releaseVersion); + variables.put(EmailVariables.USER, user); + variables.put(EmailVariables.MEMBERKEY, releasePackage.getMember().getKey()); + variables.put(EmailVariables.VIEW_RELEASE_PACKAGE_URL, clientLinkBuilder.buildViewReleasePackageLink(releasePackage.getReleasePackageId())); + variables.put(EmailVariables.VIEW_PACKAGES_URL, clientLinkBuilder.buildViewReleasesLink()); + + // Generate unsubscribe URL with affiliateId and unsubscribeKey + String unsubscribeUrl = generateUnsubscribeData(user); + variables.put(EmailVariables.UNSUBSCRIBE_URL, unsubscribeUrl); // Add unsubscribe URL to the variables + + // Generate the content using the template + String content = templateEvaluator.evaluateTemplate("releasePackageUpdatedEmail", locale, variables); + + // Get the subject for the email + String subject = templateEvaluator.getTitleFor("releasePackageUpdated", locale); + + // Send the email + mailService.sendEmail(user.getEmail(), subject, content, false, true); + } + + + // New method to generate unsubscribe key and fetch affiliateId + public String generateUnsubscribeData(User user) { + // Fetch affiliate using creator from Affiliate table (returns Affiliate, not Optional) + List affiliateList = affiliateRepository.findByCreatorIgnoreCase(user.getLogin()); + + // Check if affiliate exists and is not empty + if (affiliateList != null && !affiliateList.isEmpty()) { + Affiliate affiliate = affiliateList.get(0); // Get the first affiliate + + long affiliateId = affiliate.getAffiliateId(); // Get affiliateId + + // Generate unsubscribe key if it does not exist already + if (user.getUnsubscribeKey() == null || user.getUnsubscribeKey().isEmpty()) { + user.setUnsubscribeKey(UUID.randomUUID().toString()); // Generate new unsubscribe key + userRepository.save(user); // Save the user with the new unsubscribe key + } + + // Return the unsubscribe URL with affiliateId and unsubscribeKey + return clientLinkBuilder.buildUnsubscribeLink(affiliateId, user.getUnsubscribeKey()); + } else { + // Handle case where affiliate is not found + throw new IllegalArgumentException("Affiliate not found for the user."); + } + } + } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/AffiliateResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/AffiliateResource.java index a886513aa..579761ab4 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/AffiliateResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/AffiliateResource.java @@ -130,12 +130,12 @@ public class AffiliateResource { } else { if (member == null) { if (standingState == null) { - affiliates = affiliateRepository.findAll(pageRequest); + affiliates = affiliateRepository.findAllByDeactivatedFalse(pageRequest); } else { if (standingStateNot) { - affiliates = affiliateRepository.findByStandingStateNot(standingState, pageRequest); + affiliates = affiliateRepository.findByStandingStateNotAndDeactivatedFalse(standingState, pageRequest); } else { - affiliates = affiliateRepository.findByStandingState(standingState, pageRequest); + affiliates = affiliateRepository.findByStandingStateAndDeactivatedFalse(standingState, pageRequest); } } } else { @@ -143,14 +143,16 @@ public class AffiliateResource { affiliates = affiliateRepository.findByHomeMember(member, pageRequest); } else { if (standingStateNot) { - affiliates = affiliateRepository.findByHomeMemberAndStandingStateNot(member, standingState, pageRequest); + affiliates = affiliateRepository.findByHomeMemberAndStandingStateNotAndDeactivatedFalse(member, standingState, pageRequest); } else { - affiliates = affiliateRepository.findByHomeMemberAndStandingState(member, standingState, pageRequest); + affiliates = affiliateRepository.findByHomeMemberAndStandingStateAndDeactivatedFalse(member, standingState, pageRequest); } } } } + AffiliateSearchResult result = new AffiliateSearchResult(); + result.setAffiliates(affiliates.getContent()); result.setTotalResults(affiliates.getTotalElements()); result.setTotalPages(affiliates.getTotalPages()); diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ApplicationResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ApplicationResource.java index 52f0be7ff..fafdc9e2b 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ApplicationResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ApplicationResource.java @@ -40,7 +40,6 @@ import java.util.regex.Pattern; - @RestController public class ApplicationResource { @Resource @@ -213,7 +212,11 @@ private Affiliate findAffiliateByUsername(String username) { applications = applicationRepository.findByApprovalStateIn(approvalStates, pageRequest); } } - return new ResponseEntity(new ApplicationCollection(applications), HttpStatus.OK); + List activeApplications = applications.stream() + .filter(a -> !a.getAffiliate().isDeactivated()) // Filtering out deactivated affiliates + .toList(); + + return new ResponseEntity<>(new ApplicationCollection(activeApplications), HttpStatus.OK); } private static final Map> ORDER_BY_FIELD_MAPPINGS = new HashMap>(); diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/AuditResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/AuditResource.java index 7e77a8b4f..d77edb6b1 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/AuditResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/AuditResource.java @@ -91,7 +91,9 @@ public List findReleaseFileDownloadAuditData(@RequestBody A Map countMap = new HashMap<>(); for (PersistentAuditEvent event : result) { Map data = event.getData(); - String key = data.get("releaseFile.label"); + String key = data.get("releaseFile.label") + "|" + + data.get("releaseVersion.name") + "|" + + data.get("releasePackage.name"); countMap.computeIfAbsent(key, k -> { ReleaseFileCountDTO countDTO = new ReleaseFileCountDTO(); diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/CommercialUsageResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/CommercialUsageResource.java index 996d0ee20..58de78978 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/CommercialUsageResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/CommercialUsageResource.java @@ -97,7 +97,9 @@ public ResponseEntity getAllUsageReports( usageReports = commercialUsageRepository.searchUsageReports(searchText,UsageReportState.NOT_SUBMITTED, pageRequest); } - return new ResponseEntity<>(new CommercialUsageCollection(usageReports), HttpStatus.OK); + return new ResponseEntity<>(new CommercialUsageCollection(usageReports.stream() + .filter(a -> !a.getAffiliate().isDeactivated()) // Filtering out deactivated affiliates + .toList() ), HttpStatus.OK); } private Sort createUsageReportsSort(String orderby) { diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/DomainBlacklistResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/DomainBlacklistResource.java index bddcaa67b..c49725bda 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/DomainBlacklistResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/DomainBlacklistResource.java @@ -41,7 +41,7 @@ public Object addDomainToBlacklist(@RequestParam String domain) { DomainBlacklist newDomain = new DomainBlacklist(); - newDomain.setDomainname(domain); + newDomain.setDomainName(domain); domainBlacklistRespository.save(newDomain); @@ -52,7 +52,7 @@ public Object addDomainToBlacklist(@RequestParam String domain) { @RequestMapping(value="api/domain-blacklist/remove", method=RequestMethod.POST) @Timed public Object removeDomainFromBlacklist(@RequestParam String domain) { - domainBlacklistRespository.deleteAll(domainBlacklistRespository.findByDomainname(domain)); + domainBlacklistRespository.deleteAll(domainBlacklistRespository.findByDomainName(domain)); return new ResponseEntity<>(HttpStatus.OK); } } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/MemberResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/MemberResource.java index 80e04abcb..fd524180f 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/MemberResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/MemberResource.java @@ -7,6 +7,7 @@ import ca.intelliware.ihtsdo.mlds.repository.MemberRepository; import ca.intelliware.ihtsdo.mlds.security.AuthoritiesConstants; import ca.intelliware.ihtsdo.mlds.web.SessionService; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.AutoDeactivationMemberDTO; import ca.intelliware.ihtsdo.mlds.web.rest.dto.MemberDTO; import com.codahale.metrics.annotation.Timed; import jakarta.annotation.Resource; @@ -32,6 +33,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.Map; @RestController @@ -204,7 +206,8 @@ public ResponseEntity updateMember(@PathVariable String memberKey, @RequestBo member.setPromotePackages(body.getPromotePackages()); member.setStaffNotificationEmail(body.getStaffNotificationEmail()); - + member.setLanguage(body.getLanguage()); + member.setFooterActive(body.getFooterActive()); memberRepository.save(member); return new ResponseEntity(new MemberDTO(member), HttpStatus.OK); @@ -216,11 +219,71 @@ public ResponseEntity updateMember(@PathVariable String memberKey, @RequestBo @Timed public ResponseEntity updateMemberFeedURL(@PathVariable String memberKey, @RequestBody MemberDTO body) throws IOException { Member member = memberRepository.findOneByKey(memberKey); + + if (member == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Member not found for key: " + memberKey); + } + member.setContactEmail(body.getContactEmail()); member.setMemberOrgURL(body.getMemberOrgURL()); member.setMemberOrgName(body.getMemberOrgName()); memberRepository.save(member); return new ResponseEntity(new MemberDTO(member), HttpStatus.OK); } + @GetMapping(value = Routes.MEMBER_AUTO_DEACTIVATION, produces = "application/json") + @RolesAllowed({AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) + @Transactional + @Timed + public ResponseEntity getAutoDeactivationMemberDeatils(@PathVariable String memberKey){ + Member member=memberRepository.findOneByKey(memberKey); + if (member == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); + } + AutoDeactivationMemberDTO deactivationMemberDTO=new AutoDeactivationMemberDTO(); + deactivationMemberDTO.setUsageReports(member.getUsageReports()); + deactivationMemberDTO.setPendingApplications(member.getPendingApplication()); + deactivationMemberDTO.setInvoicesPending(member.getInvoicesPending()); + return new ResponseEntity<>((deactivationMemberDTO), HttpStatus.OK); + } + + @PutMapping(value = Routes.POST_MEMBER_AUTO_DEACTIVATION,produces = "application/json") + @RolesAllowed({AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) + @Transactional + @Timed + public ResponseEntity postAutoDeactivationMemberDetails( + @PathVariable String memberKey, + @RequestBody AutoDeactivationMemberDTO autoDeactivationDTO) { + + Member member = memberRepository.findOneByKey(memberKey); + if (member == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Member not found"); + } + + member.setInvoicesPending(autoDeactivationDTO.getInvoicesPending()); + member.setUsageReports(autoDeactivationDTO.getUsageReports()); + member.setPendingApplication(autoDeactivationDTO.getPendingApplications()); + + memberRepository.save(member); + return ResponseEntity.ok(Map.of("message", "Data Saved Successfully").toString()); + + } + + @PutMapping(value = Routes.MEMBERLANGUAGEANDFOOTER, + produces = "application/json") + @RolesAllowed({AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) + @Transactional + @Timed + public ResponseEntity updateMemberLanguageAndFooter(@PathVariable String memberKey, @RequestBody MemberDTO body) { + Member member = memberRepository.findOneByKey(memberKey); + if (member == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + member.setLanguage(body.getLanguage()); + member.setFooterActive(body.getFooterActive()); + memberRepository.save(member); + + return new ResponseEntity<>(new MemberDTO(member), HttpStatus.OK); + } } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleasePackagesResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleasePackagesResource.java index 7aeadde7d..2c860689f 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleasePackagesResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleasePackagesResource.java @@ -1,18 +1,20 @@ package ca.intelliware.ihtsdo.mlds.web.rest; -import ca.intelliware.ihtsdo.mlds.domain.File; -import ca.intelliware.ihtsdo.mlds.domain.ReleasePackage; -import ca.intelliware.ihtsdo.mlds.domain.ReleaseVersion; -import ca.intelliware.ihtsdo.mlds.repository.BlobHelper; -import ca.intelliware.ihtsdo.mlds.repository.FileRepository; -import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageRepository; +import ca.intelliware.ihtsdo.mlds.domain.*; +import ca.intelliware.ihtsdo.mlds.repository.*; import ca.intelliware.ihtsdo.mlds.security.AuthoritiesConstants; import ca.intelliware.ihtsdo.mlds.security.ihtsdo.CurrentSecurityContext; +import ca.intelliware.ihtsdo.mlds.service.ReleasePackageAccessService; import ca.intelliware.ihtsdo.mlds.service.ReleasePackagePrioritizer; +import ca.intelliware.ihtsdo.mlds.service.ReleasePackageService; import ca.intelliware.ihtsdo.mlds.service.UserMembershipAccessor; import ca.intelliware.ihtsdo.mlds.web.SessionService; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.ReleasePermissionRequestDTO; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.Resource; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; @@ -45,7 +47,6 @@ public class ReleasePackagesResource { BlobHelper blobHelper; @Autowired FileRepository fileRepository; - @Autowired SessionService sessionService; @Autowired @@ -68,6 +69,34 @@ public class ReleasePackagesResource { @Autowired ReleasePackagePrioritizer releasePackagePrioritizer; + + + @Autowired + ReleasePackageAccessRepository releasePackageAccessRepository; + + + ReleasePackageConfigRepository releasePackageConfigRepository; + + + UserRepository userRepository; + + + ReleasePackageService releasePackageService; + + + public ReleasePackagesResource(ReleasePackageAccessService releasePackageAccessService, UserRepository userRepository, ReleasePackageConfigRepository releasePackageConfigRepository, ReleasePackageService releasePackageService, SessionService sessionService) { + this.releasePackageAccessService = releasePackageAccessService; + this.userRepository = userRepository; + this.releasePackageConfigRepository = releasePackageConfigRepository; + this.releasePackageService = releasePackageService; + this.sessionService = sessionService; + } + + ReleasePackageAccessService releasePackageAccessService; + + private static final String NOT_SELECTED = "NOT_SELECTED"; + + // // //////////////////////////////////////////////////////////////////////////////////////////////////////// // // Release Packages @@ -78,17 +107,94 @@ public class ReleasePackagesResource { @Timed public ResponseEntity> getReleasePackages() { - Collection releasePackages = releasePackageRepository.findAll(); + Collection releasePackages = releasePackageRepository.findAll(); - releasePackages = filterReleasePackagesByOnline(releasePackages); + //admin role checks - no restriction to admin role returns all the packages + if(currentSecurityContext.isAdmin()){ + return new ResponseEntity<>(releasePackages, HttpStatus.OK); + } + + // split online alphabeta offline package logic + List onlinePackages = new ArrayList<>(); + List alphaBetaPackages = new ArrayList<>(); + List offlinePackages = new ArrayList<>(); + List finalMasterResult = new ArrayList<>(); + + for (ReleasePackage eachReleasePackage : releasePackages) { + releasePackageService.classifyPackage(eachReleasePackage, onlinePackages, alphaBetaPackages, offlinePackages); + } + + if (currentSecurityContext.isStaff() || currentSecurityContext.isMember()) { + Collection staffAccessiblePackages = releasePackageAccessService.getAccessiblePackagesForStaff( + releasePackages, + onlinePackages, + alphaBetaPackages, + offlinePackages, + currentSecurityContext.getStaffMemberKey() + ); + return ResponseEntity.ok(staffAccessiblePackages); + } + + if (currentSecurityContext.isUser()) { + Collection userAccessiblePackages = + releasePackageAccessService.getAccessiblePackagesForUser( + releasePackages, + onlinePackages, + alphaBetaPackages, + offlinePackages, + currentSecurityContext.getCurrentUserName() + ); + + return new ResponseEntity<>(userAccessiblePackages, HttpStatus.OK); + } + + if (!currentSecurityContext.isUser() + && !currentSecurityContext.isAdmin() + && !currentSecurityContext.isStaff()) { + + Collection result = releasePackageAccessService + .getAccessiblePackagesForUnauthenticatedUser( + releasePackages, + onlinePackages, + alphaBetaPackages, + offlinePackages + ); + + return new ResponseEntity<>(result, HttpStatus.OK); + } + + return new ResponseEntity<>(finalMasterResult, HttpStatus.OK); + } + + @GetMapping(value = Routes.ARCHIVE_RELEASE_PACKAGES, + produces = MediaType.APPLICATION_JSON_VALUE) + @RolesAllowed(AuthoritiesConstants.ADMIN) + @Timed + public ResponseEntity> getArchiveReleasePackages() { - return new ResponseEntity<>(releasePackages, HttpStatus.OK); + List releasePackages = releasePackageRepository.findAll(); + + releasePackages = filterReleasePackagesByOnline(releasePackages); + List response = releasePackages.stream() + .map(releasePackage -> { + Set archivedVersions = releasePackage.getReleaseVersions().stream() + .filter(ReleaseVersion::isArchive) + .collect(Collectors.toSet()); + if (!archivedVersions.isEmpty()) { + releasePackage.setReleaseVersions(archivedVersions); // Keep only archived versions + return releasePackage; // Include this package + } + return null; // Skip this package + }) + .filter(Objects::nonNull) // Exclude null packages + .toList(); + return new ResponseEntity<>(response, HttpStatus.OK); } - private Collection filterReleasePackagesByOnline( - Collection releasePackages) { + private List filterReleasePackagesByOnline( + List releasePackages) { - Collection result = releasePackages; + List result = releasePackages; if (!authorizationChecker.shouldSeeOfflinePackages()) { result = new ArrayList<>(); @@ -120,6 +226,7 @@ private boolean isPackagePublished(ReleasePackage releasePackage) { authorizationChecker.checkCanCreateReleasePackages(); releasePackage.setCreatedBy(currentSecurityContext.getCurrentUserName()); + releasePackage.setPermissionType(ReleasePermissionType.NOT_SELECTED); // MLDS-740 - Allow Admin to specify the member if (releasePackage.getMember() == null || !currentSecurityContext.isAdmin()) { @@ -136,20 +243,34 @@ private boolean isPackagePublished(ReleasePackage releasePackage) { return result; } - @GetMapping(value = Routes.RELEASE_PACKAGE, - produces = MediaType.APPLICATION_JSON_VALUE) + + @GetMapping(value = Routes.RELEASE_PACKAGE, produces = MediaType.APPLICATION_JSON_VALUE) @RolesAllowed({AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.USER, AuthoritiesConstants.MEMBER, AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) @Timed public ResponseEntity getReleasePackage(@PathVariable long releasePackageId) { Optional optionalReleasePackage = releasePackageRepository.findById(releasePackageId); + if (optionalReleasePackage.isEmpty()) return new ResponseEntity<>(HttpStatus.NOT_FOUND); - if (optionalReleasePackage.isEmpty()) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); + ReleasePackage releasePackage = optionalReleasePackage.get(); + String releaseType = releasePackageService.categorizePackage(releasePackage.getReleaseVersions()); + ReleasePackageConfig config = releasePackageConfigRepository.findByReleaseType(releaseType.toUpperCase()); + ReleasePackageConfig masterConfig = releasePackageConfigRepository.findByReleaseType("ALL"); + + if (currentSecurityContext.isAdmin()) { + return ResponseEntity.ok(releasePackage); } - ReleasePackage releasePackage = filterReleasePackageByAuthority(optionalReleasePackage.get()); + if (currentSecurityContext.isStaff() || currentSecurityContext.isMember()) { + if (releasePackageAccessService.isStaffOwner(releasePackage)) return ResponseEntity.ok(releasePackage); + if (releasePackageAccessService.isAdminOnly(releasePackage, config, masterConfig)) return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + return ResponseEntity.ok(releasePackage); + } - return new ResponseEntity<>(releasePackage, HttpStatus.OK); + if (currentSecurityContext.isUser()) { + return releasePackageAccessService.handleUserAccess(releasePackage, config, masterConfig, releasePackageId); + } + + return releasePackageAccessService.handlePublicAccess(releasePackage, config, masterConfig); } private ReleasePackage filterReleasePackageByAuthority(ReleasePackage releasePackage) { @@ -318,4 +439,250 @@ private File updateFile(MultipartFile multipartFile, File existingFile) throws I fileRepository.save(newFile); return newFile; } + + @PutMapping(value = Routes.RELEASE_PACKAGE_PERMISSION, produces = MediaType.APPLICATION_JSON_VALUE) + @RolesAllowed(AuthoritiesConstants.ADMIN) + @Timed + public ResponseEntity updateReleasePackageType(@PathVariable long releasePackageId, @RequestBody Map request) { + + Optional optionalReleasePackage = releasePackageRepository.findById(releasePackageId); + if (optionalReleasePackage.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + ReleasePackage releasePackage = optionalReleasePackage.get(); + authorizationChecker.checkCanEditReleasePackage(releasePackage); + String releasePackageType = (String) request.get("releasePackageType"); + if (releasePackageType != null) { + releasePackage.setPermissionType(ReleasePermissionType.valueOf(releasePackageType)); + } + + if(releasePackage.getPermissionType() == ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS) { + List users = (List) request.get("users"); + if (users != null && !users.isEmpty()) { + users.forEach(user ->{ + ReleasePackageAccess access = new ReleasePackageAccess(); + access.setReleasePackageId(releasePackage.getReleasePackageId()); + access.setUserId(Long.parseLong(user)); + releasePackageAccessRepository.save(access); + }); + } + } + + releasePackageRepository.save(releasePackage); + return new ResponseEntity<>(releasePackage, HttpStatus.OK); + } + + @PutMapping(value = Routes.RELEASE_PACKAGES_PERMISSION, produces = MediaType.APPLICATION_JSON_VALUE) + @RolesAllowed(AuthoritiesConstants.ADMIN) + @Timed + public ResponseEntity updateReleasePackagesType(@RequestBody Map request) { + releasePackageService.updateReleasePackagesPermission(request); + return new ResponseEntity<>(HttpStatus.OK); + } + + @PostMapping(value = Routes.RELEASE_PACKAGES_MASTER_PERMISSION, produces = MediaType.APPLICATION_JSON_VALUE) + @RolesAllowed(AuthoritiesConstants.ADMIN) + @Timed + public ResponseEntity updateReleaseMasterConfig(@RequestBody Map request) { + try { + releasePackageService.updateReleaseMasterConfig(request); + return new ResponseEntity<>(HttpStatus.OK); + } catch (RuntimeException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); + } + } + + + @GetMapping(value = "/api/releasePermission", produces = MediaType.APPLICATION_JSON_VALUE) + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity> getReleasePermissions() { + List permissionDTOList = releasePackageRepository.findAll().stream() + .filter(releasePackage -> releasePackage.getPermissionType() != ReleasePermissionType.NOT_SELECTED) + .map(releasePackage -> new ReleasePermissionRequestDTO( + releasePackage.getReleasePackageId(), + releasePackage.getName(), + releasePackage.getPermissionType()) + ) + .toList(); + + return ResponseEntity.ok(permissionDTOList); + } + + + @GetMapping(value = "/api/masterReleasePermission", produces = MediaType.APPLICATION_JSON_VALUE) + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity> getMasterReleasePermissions() { + List masterPermissionList = releasePackageConfigRepository.findAll().stream() + .filter(releasePackage -> !NOT_SELECTED.equals(releasePackage.getReleasePermissionType())) + .toList(); + + return ResponseEntity.ok(masterPermissionList); + } + + + @GetMapping("/api/{releasePackageId}/usersAccess") + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity> getUsersByReleasePackageId(@PathVariable Long releasePackageId) { + List logins = releasePackageAccessRepository.findLoginsByReleasePackageId(releasePackageId); + return ResponseEntity.ok(logins); + } + + @GetMapping("/api/releaseTypes") + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity> getAllReleaseTypes() { + List releaseTypes = releasePackageConfigRepository.findAll() + .stream() + .map(ReleasePackageConfig::getReleaseType) + .distinct() + .toList(); + + return ResponseEntity.ok(releaseTypes); + } + + + @GetMapping("/api/masterUsersAccess") + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity> getMasterAccessUsersByReleasePackageId(@RequestParam String releaseType) { + ReleasePackageConfig result = releasePackageConfigRepository.findByReleaseType(releaseType); + + if (result == null || result.getUserList() == null || result.getUserList().isEmpty()) { + return ResponseEntity.ok(Collections.emptyList()); + } + + List userIds; + + try { + userIds = new ObjectMapper().readValue(result.getUserList(), new TypeReference>() {}); + } catch (JsonProcessingException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Collections.singletonList("Failed to parse user list")); + } + + if (userIds.isEmpty()) { + return ResponseEntity.ok(Collections.emptyList()); + } + + List logins = releasePackageAccessRepository.findLoginsByUserIds(userIds); + return ResponseEntity.ok(logins); + } + private static final String RELEASE_ID = "releaseId"; + + @PutMapping(value = "/api/userAccessRevoke", produces = MediaType.APPLICATION_JSON_VALUE) + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity updateIndividualUserAccess(@RequestBody Map request) { + String releaseId = request.get(RELEASE_ID) != null ? request.get(RELEASE_ID).toString() : null; + String requestUser = (String) request.get("user"); + + try { + String resultMessage = releasePackageService.revokeIndividualUserAccess(releaseId, requestUser); + if (resultMessage.equals("User access revoked successfully.")) { + return ResponseEntity.ok(resultMessage); + } + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(resultMessage); + } catch (JsonProcessingException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error processing user list."); + } + } + + @PutMapping(value = "/api/releaseAccessRevoke", produces = MediaType.APPLICATION_JSON_VALUE) + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity releaseAccessRevoke(@RequestBody Map request) { + String releaseId = request.get(RELEASE_ID) != null ? request.get(RELEASE_ID).toString() : null; + + String resultMessage = releasePackageService.revokeAllReleaseAccess(releaseId); + if (resultMessage.equals("User access revoked successfully.")) { + return ResponseEntity.ok(resultMessage); + } + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(resultMessage); + } + + + @GetMapping("/api/viewVisiblity/{releasePackageId}") + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity getVisibilityDetails(@PathVariable Long releasePackageId) { + String releaseType = "ALL"; + ReleasePackageConfig masterPermission = releasePackageService.getMasterPermission(releaseType); + + if (masterPermission != null && Boolean.FALSE.equals(masterPermission.getActive())) { + Optional releasePackage = releasePackageRepository.findById(releasePackageId); + + if (releasePackage.isPresent()) { + PermissionVisibilityResponse response = releasePackageService.getVisibilityDetails(releasePackage.get()); + return ResponseEntity.ok(response); + } else { + return ResponseEntity.notFound().build(); + } + } else { + String permissionType = masterPermission != null ? masterPermission.getReleasePermissionType() : ""; + PermissionVisibilityResponse response = new PermissionVisibilityResponse(true, permissionType, releaseType); + return ResponseEntity.ok(response); + } + } + + + @PostMapping("/api/releasePackages/checkConfigPermissionType") + @RolesAllowed(AuthoritiesConstants.ADMIN) + @Timed + public ResponseEntity checkReleaseMasterConfig(@RequestBody Map request) { + String releasePackageType = (String) request.get("releaseType"); + + List allReleasePackages = releasePackageRepository.findAll(); + List configuredReleaseConfigs = releasePackageConfigRepository.findByReleasePermissionTypeNot(NOT_SELECTED); + + boolean hasAnyConfiguredPermission = allReleasePackages.stream() + .anyMatch(rp -> rp.getPermissionType() != ReleasePermissionType.NOT_SELECTED); + + boolean hasMasterConfigured = configuredReleaseConfigs.stream() + .anyMatch(rp -> !"ALL".equals(rp.getReleaseType())); + + if (Objects.equals(releasePackageType, "ALL")) { + return ResponseEntity.ok(hasAnyConfiguredPermission || hasMasterConfigured); + } else { + boolean hasAllTypeConfigured = configuredReleaseConfigs.stream() + .anyMatch(rp -> "ALL".equals(rp.getReleaseType())); + + if (hasAllTypeConfigured) { + return ResponseEntity.ok(true); + } + + Set categorizationResults = allReleasePackages.stream() + .filter(rp -> rp.getPermissionType() != ReleasePermissionType.NOT_SELECTED) + .map(rp -> releasePackageService.categorizePackage(rp.getReleaseVersions())) + .collect(Collectors.toSet()); + + return ResponseEntity.ok(categorizationResults.contains(releasePackageType.toLowerCase())); + } + } + + + @PostMapping("/api/releasePackages/checkUpdatePermissionType") + @RolesAllowed(AuthoritiesConstants.ADMIN) + @Timed + public ResponseEntity checkReleasePackageUpdate(@RequestBody Map request) { + List releasePackages = (List) request.get("releases"); + + if(Boolean.TRUE.equals(releasePackageConfigRepository.findByReleaseType("ALL").getActive())){ + return ResponseEntity.ok(true); + } + + List masterConfigurationList = releasePackageConfigRepository.findByReleasePermissionTypeNot(NOT_SELECTED).stream() + .filter(rp -> !"ALL".equals(rp.getReleaseType())).toList(); + if(!masterConfigurationList.isEmpty()) { + List updatereleaseList = releasePackageRepository.findAllByReleasePackageIdIn(releasePackages); + Set categorizationResults = new HashSet<>(); + for (ReleasePackage updateReleasePackage : updatereleaseList) { + String type = releasePackageService.categorizePackage(updateReleasePackage.getReleaseVersions()); + categorizationResults.add(type); + } + + for (ReleasePackageConfig releasePackageConfig : masterConfigurationList) { + if (categorizationResults.contains(releasePackageConfig.getReleaseType().toLowerCase())) { + return ResponseEntity.ok(true); + } + } + } + + return ResponseEntity.ok(false); + } + } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseVersionsResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseVersionsResource.java index 19f982c22..01cdeb128 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseVersionsResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseVersionsResource.java @@ -6,6 +6,7 @@ import ca.intelliware.ihtsdo.mlds.repository.ReleaseVersionRepository; import ca.intelliware.ihtsdo.mlds.security.AuthoritiesConstants; import ca.intelliware.ihtsdo.mlds.security.ihtsdo.CurrentSecurityContext; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.ReleaseVersionCheckViewDTO; import com.codahale.metrics.annotation.Timed; import com.google.common.base.Objects; import jakarta.annotation.Resource; @@ -19,6 +20,8 @@ import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -125,11 +128,12 @@ public class ReleaseVersionsResource { releaseVersion.setName(body.getName()); releaseVersion.setDescription(body.getDescription()); - releaseVersion.setReleaseType(body.getReleaseType()); + if(!releaseVersion.isArchive()) { + releaseVersion.setReleaseType(body.getReleaseType()); + } releaseVersion.setPublishedAt(body.getPublishedAt()); releaseVersion.setSummary(body.getSummary()); - releaseVersion.setReleaseType(body.getReleaseType()); releaseVersion.setVersionDependentURI(body.getVersionDependentURI()); releaseVersion.setVersionDependentDerivativeURI(body.getVersionDependentDerivativeURI()); releaseVersion.setVersionURI(body.getVersionURI()); @@ -233,4 +237,18 @@ public ResponseEntity updateArchive(@PathVariable long releaseVe return new ResponseEntity<>(releaseVersion, HttpStatus.OK); } + + @GetMapping(value = Routes.RELEASE_VERSION_DEPENDENCY_NAMES, + produces = MediaType.APPLICATION_JSON_VALUE) + @Transactional + @RolesAllowed({AuthoritiesConstants.STAFF, AuthoritiesConstants.ADMIN}) + @Timed + public List getDependentVersionsNames(@PathVariable long releaseVersionId) { + ReleaseVersion releaseVersion = releaseVersionRepository.findById(releaseVersionId).orElse(null); + if (releaseVersion == null) { + return new ArrayList<>(); + } + return releaseVersionRepository.getDependentVersionNames(releaseVersion.getVersionURI()); + } + } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/Routes.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/Routes.java index 2a304303e..7c0ad8239 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/Routes.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/Routes.java @@ -10,6 +10,7 @@ public class Routes { public static final String MEMBER = "/api/members/{memberKey}"; // FIXME MLDS-309 spelling + public static final String MEMBERLANGUAGEANDFOOTER = "/api/members/{memberKey}/language"; public static final String MEMBER_LICENSE = "/api/members/{memberKey}/license"; public static final String MEMBER_LOGO = "/api/members/{memberKey}/logo"; @@ -113,6 +114,7 @@ public class Routes { * - POST to create */ public static final String RELEASE_PACKAGES = "/api/releasePackages"; + public static final String ARCHIVE_RELEASE_PACKAGES = "/api/archiveReleasePackages"; /** * control endpoint for single release package: @@ -217,6 +219,15 @@ public class Routes { public static final String MEMBER_FEED_URL = "/api/members/{memberKey}/memberFeedUrl"; public static final String FEED_URL = "/api/feed"; + public static final String MEMBER_AUTO_DEACTIVATION = "/api/members/{memberKey}"; + public static final String POST_MEMBER_AUTO_DEACTIVATION = "/api/members/{memberKey}/autoDeactivation"; + + static final String RELEASE_VERSION_DEPENDENCY_NAMES = "/api/getVersionDependencyNames/{releaseVersionId}"; + + + public static final String RELEASE_PACKAGES_PERMISSION = "/api/releasePackages/updatePermissionType"; + public static final String RELEASE_PACKAGES_MASTER_PERMISSION = "/api/releasePackages/ConfigPermissionType"; + public static final String RELEASE_PACKAGE_PERMISSION = "/api/releasePackages/updatePermissionType/{releasePackageId}"; } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UriDownloader.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UriDownloader.java index cae634632..45f257675 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UriDownloader.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UriDownloader.java @@ -28,6 +28,11 @@ import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service public class UriDownloader { @@ -42,7 +47,8 @@ public class UriDownloader { @Value("${aws.region}") private String awsRegion; - + @Value("${aws.regions}") + private String awsRegions; private final Logger log = LoggerFactory.getLogger(UriDownloader.class); private S3Location s3BucketName; @@ -88,7 +94,7 @@ S3Location determineS3Location(String url) { } public int downloadS3(S3Location s3Location, String downloadUrl, HttpServletRequest clientRequest, HttpServletResponse clientResponse) - throws IOException { + throws IOException { log.debug("Attempting to download {} from S3", s3Location.toString()); log.debug(s3Location.bucket); this.s3BucketName = s3Location; @@ -107,12 +113,65 @@ public int downloadS3(S3Location s3Location, String downloadUrl, HttpServletRequ return clientResponse.getStatus(); } + /** + * Returns an initialized S3Client based on configured AWS region properties. + * + * Configuration details: + * - Default region: `aws.region` + * - Multi-bucket specific region mapping: `aws.regions` + * + * Format in application.properties: + * + * # Default region fallback + * aws.region=us-east-1 + * + * # AWS Regions Defined Here + * # Default will be aws.region, and if new regions are to be added, + * # kindly follow the same format as below: + * aws.regions=ihtsdo-mlds.de:eu-central-1,snomed-mlds.in:ap-south-1 + * + * Each entry should be in the format: bucket-name:region + * Multiple entries are comma-separated. + * + * @return S3Client - either a live AWS client or Offline client + */ public S3Client getS3Client() throws IOException { - S3Client s3Client = null; - log.debug("Configuring " + (s3Offline ? "offline" : "online") + " s3 client."); - String regionToUse = awsRegion; - if ("ihtsdo-mlds.de".equals(s3BucketName.bucket)) { - regionToUse = "eu-central-1"; + S3Client s3Client; + // Default region from application.properties + String regionsToUse; + String regionToUse = awsRegions; + + Map> regionMap = new HashMap<>(); + + + // Check if awsRegions is not null or empty before processing + if (awsRegions != null && !awsRegions.isEmpty()) { + regionMap = Arrays.stream(awsRegions.split(",")) + .map(entry -> entry.split(":")) // Split bucket name and regions + .filter(parts -> { + boolean valid = parts.length == 2; + if (!valid) log.warn("Skipping invalid entry: {}", Arrays.toString(parts)); + return valid; + }) + .collect(Collectors.toMap( + parts -> parts[0], // Bucket name + parts -> Arrays.asList(parts[1].split(",")) // List of regions + )); + + + regionMap.forEach((bucket, regions) -> log.debug("Bucket: {} -> Regions: {}", bucket, regions)); + } else { + log.warn("awsRegions property is empty or null!"); + } + + // If the bucket exists in the map, get the first region (or use any logic to select one) + if (regionMap.containsKey(s3BucketName.bucket)) { + List regions = regionMap.get(s3BucketName.bucket); + regionsToUse = regions.get(0); // Choose the first region (modify logic if needed) + log.debug("Available regions for {}: {}", s3BucketName.bucket, regions); + }else + { + regionsToUse=awsRegion; } @@ -121,9 +180,9 @@ public S3Client getS3Client() throws IOException { } else { s3Client = new S3ClientImpl(software.amazon.awssdk.services.s3.S3Client.builder() .credentialsProvider(InstanceProfileCredentialsProvider.create()) - .region(Region.of(regionToUse)) + .region(Region.of(regionsToUse)) .build()); - log.debug("s3Client:", s3Client); + log.debug("s3Client initialized with region: {}", regionToUse); } return s3Client; } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UserNotifier.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UserNotifier.java index 9940ab0fb..ecb703580 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UserNotifier.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UserNotifier.java @@ -25,10 +25,10 @@ public void notifyReleasePackageUpdated(ReleaseVersion releaseVersion) { ReleasePackage releasePackage = releaseVersion.getReleasePackage(); Member member = releasePackage.getMember(); for (User user : userMembershipCalculator.approvedReleaseUsersWithAnyMembership(member)) { - if (user.getAcceptNotifications() && - !(Member.KEY_IHTSDO.equals(member.getKey()) && user.getCountryNotificationsOnly())) { + if (user.getAcceptNotifications() && !(Member.KEY_IHTSDO.equals(member.getKey()) && user.getCountryNotificationsOnly())) { releasePackageUpdatedEmailSender.sendRelasePackageUpdatedEmail(user, releasePackage, releaseVersion); } } + } } diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UserResource.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UserResource.java index 0069555a5..260021275 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UserResource.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/UserResource.java @@ -1,17 +1,29 @@ package ca.intelliware.ihtsdo.mlds.web.rest; +import ca.intelliware.ihtsdo.mlds.domain.Affiliate; import ca.intelliware.ihtsdo.mlds.domain.User; +import ca.intelliware.ihtsdo.mlds.repository.AffiliateRepository; import ca.intelliware.ihtsdo.mlds.repository.UserRepository; import ca.intelliware.ihtsdo.mlds.security.AuthoritiesConstants; +import ca.intelliware.ihtsdo.mlds.service.UserService; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.AffiliateDetailsResponseDTO; import com.codahale.metrics.annotation.Timed; import jakarta.annotation.security.RolesAllowed; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; +import java.util.*; + /** * REST controller for managing users. */ @@ -22,7 +34,16 @@ public class UserResource { private final Logger log = LoggerFactory.getLogger(UserResource.class); @Autowired - private UserRepository userRepository; + UserRepository userRepository; + + public UserResource(UserService userService, AffiliateRepository affiliateRepository) { + this.userService = userService; + this.affiliateRepository = affiliateRepository; + } + + AffiliateRepository affiliateRepository; + + UserService userService; @RequestMapping(value = "/users", method = RequestMethod.GET, @@ -50,4 +71,114 @@ public User getUser(@PathVariable String login, HttpServletResponse response) { } return user; } + + @PostMapping(value = "/getUserDetails") + @Timed + @RolesAllowed(AuthoritiesConstants.ADMIN) + public ResponseEntity getUserDetails(@RequestParam String login, @RequestParam Long affiliateDetailsId) { + log.debug("REST request to get User : {}", login); + + AffiliateDetailsResponseDTO responseDTO = userService.getAffiliateDetails(login); + + // If no affiliate details are found, return an empty DTO with HTTP 200 + if (responseDTO.getAffiliateDetails() == null && responseDTO.getAffiliate().isEmpty()) { + return ResponseEntity.ok(responseDTO); + } + + return ResponseEntity.ok(responseDTO); + } + + + @PostMapping(value = "/updatePrimaryEmail") + @RolesAllowed(AuthoritiesConstants.ADMIN) + @Timed + public ResponseEntity updatePrimaryEmail(@RequestParam String login, @RequestParam String updatedEmail) { + try { + + userService.updatePrimaryEmail(login, updatedEmail); + return ResponseEntity.ok("Primary email updated successfully"); + } catch (NoSuchElementException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User or related data not found"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred while updating the email"); + } + } + @GetMapping(value = "/testRun") + @Timed + public ResponseEntity testRun() { + try { + + userService.removeUsageReports(); + return ResponseEntity.ok("Pendind Applications Removed successfully"); + } catch (NoSuchElementException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User or related data not found"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred while removing the Pendind Applications"); + } + } + + @PostMapping("/unsubscribenotification/{affiliateId}/{key}") + public ResponseEntity unsubscribeOnce( + @PathVariable Long affiliateId, + @PathVariable String key) { + + // Step 1: Get the Affiliate using the affiliateId + Optional affiliateOpt = affiliateRepository.findById(affiliateId); + + if (affiliateOpt.isPresent()) { + Affiliate affiliate = affiliateOpt.get(); + + // Step 2: Get the creator from the Affiliate + String creatorLogin = affiliate.getCreator(); + + // Step 3: Fetch the User by matching the creator's login (no need for Optional here) + User user = userRepository.findByLoginIgnoreCase(creatorLogin); + + if (user != null) { + // Step 4: Check if the key matches + if (!key.equals(user.getUnsubscribeKey())) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired unsubscribe link."); + } + + Boolean accept = user.getAcceptNotifications(); + if (Boolean.FALSE.equals(accept)) { + return ResponseEntity.status(HttpStatus.GONE).body("This unsubscribe link has already been used."); + } + + + // Step 6: Unsubscribe the user by setting acceptNotifications to false + user.setAcceptNotifications(false); + + // Step 7: Invalidate the unsubscribe key by generating a new one + user.setUnsubscribeKey(UUID.randomUUID().toString()); + userRepository.save(user); + + return ResponseEntity.ok("You have successfully unsubscribed."); + } + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found."); + } + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Affiliate not found."); + } + @GetMapping("/usersList") + @RolesAllowed(AuthoritiesConstants.ADMIN) + @Timed + public ResponseEntity>> getUsersWithAuthorities() { + + List users = userRepository.findAll(); + + List> userLogins = users.stream() + .map(user -> { + Map map = new HashMap<>(); + map.put("userId", user.getUserId()); + map.put("login", user.getLogin()); + return map; + }) + .toList(); + + return ResponseEntity.ok(userLogins); + } } + + diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/AffiliateDetailsResponseDTO.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/AffiliateDetailsResponseDTO.java new file mode 100644 index 000000000..b6c70f220 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/AffiliateDetailsResponseDTO.java @@ -0,0 +1,47 @@ +package ca.intelliware.ihtsdo.mlds.web.rest.dto; + +import ca.intelliware.ihtsdo.mlds.domain.Affiliate; +import ca.intelliware.ihtsdo.mlds.domain.AffiliateDetails; +import ca.intelliware.ihtsdo.mlds.domain.User; + +import java.util.List; + +public class AffiliateDetailsResponseDTO { + private User user; + private AffiliateDetails affiliateDetails; + private List affiliate; + + // Constructors + public AffiliateDetailsResponseDTO() {} + + public AffiliateDetailsResponseDTO(User user, AffiliateDetails affiliateDetails, List affiliate) { + this.user = user; + this.affiliateDetails = affiliateDetails; + this.affiliate = affiliate; + } + + // Getters and Setters + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public AffiliateDetails getAffiliateDetails() { + return affiliateDetails; + } + + public void setAffiliateDetails(AffiliateDetails affiliateDetails) { + this.affiliateDetails = affiliateDetails; + } + + public List getAffiliate() { + return affiliate; + } + + public void setAffiliate(List affiliate) { + this.affiliate = affiliate; + } +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/AutoDeactivationMemberDTO.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/AutoDeactivationMemberDTO.java new file mode 100644 index 000000000..f2747b6a9 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/AutoDeactivationMemberDTO.java @@ -0,0 +1,33 @@ +package ca.intelliware.ihtsdo.mlds.web.rest.dto; + +public class AutoDeactivationMemberDTO { + + private int pendingApplications; + private int usageReports; + + public int getInvoicesPending() { + return invoicesPending; + } + + public void setInvoicesPending(int invoicesPending) { + this.invoicesPending = invoicesPending; + } + + public int getPendingApplications() { + return pendingApplications; + } + + public void setPendingApplications(int pendinginvoices) { + this.pendingApplications = pendinginvoices; + } + + public int getUsageReports() { + return usageReports; + } + + public void setUsageReports(int usageReports) { + this.usageReports = usageReports; + } + + private int invoicesPending; +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/MemberDTO.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/MemberDTO.java index 190421a65..985056cc2 100644 --- a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/MemberDTO.java +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/MemberDTO.java @@ -39,6 +39,24 @@ public class MemberDTO { private String name; private FileDTO logo; + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public Boolean getFooterActive() { + return footerActive; + } + + public void setFooterActive(Boolean footerActive) { + this.footerActive = footerActive; + } + + private String language; + private Boolean footerActive; // Sensitive email addresses - intent is to only reveal to other staff/admins private String staffNotificationEmail; @@ -64,6 +82,8 @@ public MemberDTO(Member member) { this.memberOrgURL=member.getMemberOrgURL(); this.memberOrgName=member.getMemberOrgName(); this.contactEmail=member.getContactEmail(); + this.language=member.getLanguage(); + this.footerActive=member.getFooterActive(); } public Long getMemberId() { diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/ReleasePermissionRequestDTO.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/ReleasePermissionRequestDTO.java new file mode 100644 index 000000000..7f4aae61b --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/ReleasePermissionRequestDTO.java @@ -0,0 +1,39 @@ +package ca.intelliware.ihtsdo.mlds.web.rest.dto; + +import ca.intelliware.ihtsdo.mlds.domain.ReleasePermissionType; + +public class ReleasePermissionRequestDTO { + private Long releasePackageId; + private String name; + private ReleasePermissionType permissionType; + + public ReleasePermissionRequestDTO(Long releasePackageId, String name, ReleasePermissionType permissionType) { + this.releasePackageId = releasePackageId; + this.name = name; + this.permissionType = permissionType; + } + + public Long getReleasePackageId() { + return releasePackageId; + } + + public void setReleasePackageId(Long releasePackageId) { + this.releasePackageId = releasePackageId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ReleasePermissionType getPermissionType() { + return permissionType; + } + + public void setPermissionType(ReleasePermissionType permissionType) { + this.permissionType = permissionType; + } +} diff --git a/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/ReleaseVersionCheckViewDTO.java b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/ReleaseVersionCheckViewDTO.java new file mode 100644 index 000000000..665cd3d19 --- /dev/null +++ b/src/main/java/ca/intelliware/ihtsdo/mlds/web/rest/dto/ReleaseVersionCheckViewDTO.java @@ -0,0 +1,6 @@ +package ca.intelliware.ihtsdo.mlds.web.rest.dto; + +public interface ReleaseVersionCheckViewDTO { + String getReleaseVersionName(); + Long getReleasePackageId(); +} diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 6f8fe859a..65936bc62 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -4,6 +4,13 @@ ims.cookie=local-ims-ihtsdo # Swagger Configuration springdoc.api-docs.path=/api-docs +# Cron Expression Scheduler Configuration +scheduler.remove-invoices-pending.cron=0 0 15 * * ? +scheduler.remove-pending-application.cron=0 0 16 * * ? +scheduler.remove-usage-reports.cron=0 0 17 * * ? + +aws.region=us-east-1 +aws.regions=ihtsdo-mlds.de:eu-central-1,snomed-mlds.in:ap-south-1 #spring.datasource.dataSourceClassName=com.mysql.cj.jdbc.MysqlDataSource spring.datasource.data-source-class-name=com.mysql.cj.jdbc.MysqlDataSource @@ -58,4 +65,4 @@ webauth.applicationName=mlds spring.thymeleaf.mode=XHTML spring.thymeleaf.cache=false -aws.region=us-east-1 + diff --git a/src/main/resources/config/liquibase/changelog/db-changelog-002.xml b/src/main/resources/config/liquibase/changelog/db-changelog-002.xml index b9583d8fe..65f37c637 100644 --- a/src/main/resources/config/liquibase/changelog/db-changelog-002.xml +++ b/src/main/resources/config/liquibase/changelog/db-changelog-002.xml @@ -155,5 +155,581 @@ + + Add deactivated column in affiliate table + + + + + + + + 9:04523831040d5448be91706a5e34dbc0 + Re-add pending_application, invoices_pending, and usage_reports columns to member table with default 0 + + + + + + + + + + + + + + Add last_processed timestamp column to affiliate, commercial_usage, and application tables with default NULL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rename member table columns from camel case to snake case + + + + + + + + + + + Rename column releasePackageURI to release_package_uri if it exists + + + + + + + + + + Rename columns in release_version Table + + + + + + + + + + + Rename column domainname to domain_name in email_domain_blacklist table + + + + + + + + Set empty file_size values to NULL before changing data type + + + file_size = '' + + Change file_size column to BIGINT in release_file table + + + + + + + + + + + + + + Change column data types to VARCHAR(255) in affiliate_details table + + + + + + + + + + + + + + + + + + + + + + Change column data types to VARCHAR(255) in affiliate_details table + + + + + + + + + + + + + + + Rename table t_authority to authority if it exists + + + + + + + + Rename table t_user_authority to user_authority if it exists + + + + + + + + Rename table t_persistent_audit_event to persistent_audit_event if it exists + + + + + + + + Rename table t_persistent_audit_event_data to persistent_audit_event_data if it exists + + + + + + + + Rename table t_persistent_token to persistent_token if it exists + + + + + + + + Rename table t_user to user if it exists + + + + + + + + + + + + + + + Rename keys for persistent_audit_event + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rename index name for release_package + + + + + + + + + + + + Rename index name for release_version + + + + + + + + + + + + Drop table user_registration if it exists + + + + + + + + Drop table event if it exists + + + + + + + + + + Add unsubscribe_key column to user table + + + + + + + Add ReleaseType Column + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Add Release Config Table + + + + + + + + + + + + + Add User list Column + + + + + + + Add isActive Column + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `key` = 'IHTSDO' AND `language` IS NULL + + + + + `key` = 'AD' AND `language` IS NULL + + + + `key` = 'AR' AND `language` IS NULL + + + + `key` = 'AT' AND `language` IS NULL + + + + `key` = 'BE' AND `language` IS NULL + + + + `key` = 'BN' AND `language` IS NULL + + + + `key` = 'CL' AND `language` IS NULL + + + + `key` = 'CZ' AND `language` IS NULL + + + + `key` = 'HR' AND `language` IS NULL + + + + `key` = 'CY' AND `language` IS NULL + + + + `key` = 'DE' AND `language` IS NULL + + + + `key` = 'DK' AND `language` IS NULL + + + + `key` = 'EE' AND `language` IS NULL + + + + `key` = 'FI' AND `language` IS NULL + + + + `key` = 'HU' AND `language` IS NULL + + + + `key` = 'IS' AND `language` IS NULL + + + + `key` = 'IE' AND `language` IS NULL + + + + `key` = 'IL' AND `language` IS NULL + + + + `key` = 'IN' AND `language` IS NULL + + + + `key` = 'ID' AND `language` IS NULL + + + + `key` = 'JM' AND `language` IS NULL + + + + `key` = 'JO' AND `language` IS NULL + + + + `key` = 'LU' AND `language` IS NULL + + + + `key` = 'MY' AND `language` IS NULL + + + + `key` = 'MT' AND `language` IS NULL + + + + `key` = 'NL' AND `language` IS NULL + + + + `key` = 'NZ' AND `language` IS NULL + + + + `key` = 'NO' AND `language` IS NULL + + + + `key` = 'PL' AND `language` IS NULL + + + + `key` = 'PT' AND `language` IS NULL + + + + `key` = 'SI' AND `language` IS NULL + + + + `key` = 'KR' AND `language` IS NULL + + + + `key` = 'SE' AND `language` IS NULL + + + + `key` = 'CH' AND `language` IS NULL + + + + `key` = 'TH' AND `language` IS NULL + + + + `key` = 'UY' AND `language` IS NULL + + + + + + `footer_active` IS NULL + + diff --git a/src/main/resources/db-changelog.xml b/src/main/resources/db-changelog.xml index 33841c75f..8703f452a 100644 --- a/src/main/resources/db-changelog.xml +++ b/src/main/resources/db-changelog.xml @@ -3,17 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> - - - - - - - - - - - + 7:685a57551698e8e61c2667a2bbc55f89 @@ -63,31 +53,6 @@ - - Create an event log so we can record TOS acceptance - - - - - - - - - - - - - - - - - - - - - - - 7:178964a1a1c900a14b2cb2fadf1da5d3 diff --git a/src/main/resources/mails/releasePackageUpdatedEmail.html b/src/main/resources/mails/releasePackageUpdatedEmail.html index 581826120..19c2cb8b7 100644 --- a/src/main/resources/mails/releasePackageUpdatedEmail.html +++ b/src/main/resources/mails/releasePackageUpdatedEmail.html @@ -1,28 +1,33 @@ - - SNOMED International - MLDS Release Package Updated - - + + SNOMED International - MLDS Release Package Updated + + - -

- Dear user, -

-

- The following release package has been updated: ${releasePackage.name}. -
-
You can view and download the release package by visiting the IHTSD-MLDS system. -

-

- View Release Package -

-

- Regards, -
- SNOMED International - MLDS -

- + +

+ Dear user, +

+

+ The following release package has been updated: ${releasePackage.name}. +
+
You can view and download the release package by visiting the IHTSD-MLDS system. +

+

+ View Release Package +

+

+ Regards, +
+ SNOMED International - MLDS +

+ + +

+ Unsubscribe from these notifications +

+ diff --git a/src/main/webapp/views/admin/blocklist.html b/src/main/webapp/views/admin/blocklist.html index b2f990198..f2f489400 100644 --- a/src/main/webapp/views/admin/blocklist.html +++ b/src/main/webapp/views/admin/blocklist.html @@ -4,7 +4,7 @@ -
+
@@ -22,11 +22,11 @@

Blocklist Domains

- {{domain.domainname}} - + {{domain.domainName}} +
- \ No newline at end of file + diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/service/AffiliateDeleterTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/service/AffiliateDeleterTest.java index 78dfbcc8a..4bfe4513a 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/service/AffiliateDeleterTest.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/service/AffiliateDeleterTest.java @@ -144,7 +144,7 @@ public void deleteAffiliateWithAllData() throws Exception { assertEquals(1,matchingNativeRecords("SELECT affiliate_id FROM affiliate WHERE affiliate_id="+affiliate.getAffiliateId())); assertEquals(1,matchingNativeRecords("SELECT application_id FROM application WHERE application_id="+primaryApplication.getApplicationId())); assertEquals(1,matchingNativeRecords("SELECT application_id FROM application WHERE application_id="+extensionApplication.getApplicationId())); - assertEquals(1,matchingNativeRecords("SELECT user_id FROM T_USER WHERE user_id="+user.getUserId())); + assertEquals(1,matchingNativeRecords("SELECT user_id FROM user WHERE user_id="+user.getUserId())); assertEquals(1,matchingNativeRecords("SELECT commercial_usage_id FROM commercial_usage WHERE commercial_usage_id="+commercialUsage.getCommercialUsageId())); assertEquals(1,matchingNativeRecords("SELECT affiliate_details_id FROM affiliate_details WHERE affiliate_details_id="+affiliateDetails.getAffiliateDetailsId())); assertEquals(1,matchingNativeRecords("SELECT affiliate_details_id FROM affiliate_details WHERE affiliate_details_id="+affiliateExtensionDetails.getAffiliateDetailsId())); @@ -219,7 +219,7 @@ public void deleteAffiliateWithSharedAffiliateDetails() throws Exception { assertEquals(1,matchingNativeRecords("SELECT affiliate_id FROM affiliate WHERE affiliate_id="+affiliate.getAffiliateId())); assertEquals(1,matchingNativeRecords("SELECT application_id FROM application WHERE application_id="+primaryApplication.getApplicationId())); assertEquals(1,matchingNativeRecords("SELECT application_id FROM application WHERE application_id="+extensionApplication.getApplicationId())); - assertEquals(1,matchingNativeRecords("SELECT user_id FROM T_USER WHERE user_id="+user.getUserId())); + assertEquals(1,matchingNativeRecords("SELECT user_id FROM user WHERE user_id="+user.getUserId())); assertEquals(1,matchingNativeRecords("SELECT commercial_usage_id FROM commercial_usage WHERE commercial_usage_id="+commercialUsage.getCommercialUsageId())); assertEquals(1,matchingNativeRecords("SELECT affiliate_details_id FROM affiliate_details WHERE affiliate_details_id="+affiliateSharedDetails.getAffiliateDetailsId())); assertEquals(1,matchingNativeRecords("SELECT affiliate_details_id FROM affiliate_details WHERE affiliate_details_id="+affiliateExtensionDetails.getAffiliateDetailsId())); diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageServiceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageServiceTest.java new file mode 100644 index 000000000..9756ec660 --- /dev/null +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/service/ReleasePackageServiceTest.java @@ -0,0 +1,239 @@ +package ca.intelliware.ihtsdo.mlds.service; + +import ca.intelliware.ihtsdo.mlds.domain.*; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageAccessRepository; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageConfigRepository; +import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageRepository; +import ca.intelliware.ihtsdo.mlds.repository.UserRepository; +import ca.intelliware.ihtsdo.mlds.security.ihtsdo.CurrentSecurityContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.HttpStatus; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ReleasePackageServiceTest { + + @Mock + private ReleasePackageConfigRepository releasePackageConfigRepository; + + @Mock + private ReleasePackageRepository releasePackageRepository; + + @Mock + private ObjectMapper objectMapper; + + @Mock + private ReleasePackageAccessRepository releasePackageAccessRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private CurrentSecurityContext currentSecurityContext; + + @InjectMocks + private ReleasePackageService releasePackageService; + + @InjectMocks + private ReleasePackageAccessService releasePackageAccessService; + + private User testUser; + private ReleasePackage testPackage; + private ReleasePackageConfig config; + private ReleasePackageConfig masterConfig; + + + @Before + public void setup() { + testUser = new User(); + testUser.setUserId(1L); + testUser.setLogin("user"); + + testPackage = new ReleasePackage(); + testPackage.setPermissionType(ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS); + + config = new ReleasePackageConfig(); + config.setReleasePermissionType("EVERYONE"); + + masterConfig = new ReleasePackageConfig(); + masterConfig.setReleasePermissionType("ADMIN_ONLY"); + } + + @Test + public void shouldUpdateReleaseMasterConfigSuccessfully() throws JsonProcessingException { + + Map request = new HashMap<>(); + request.put("releaseType", "ONLINE"); + request.put("releasePermissionType", "ADMIN_STAFF_SELECTED_USERS"); + List users = List.of("101", "102"); + request.put("users", users); + + String userListJson = "[\"101\",\"102\"]"; + + ReleasePackageConfig existingConfig = new ReleasePackageConfig(); + existingConfig.setReleaseType("ONLINE"); + + when(releasePackageConfigRepository.findByReleaseType("ONLINE")).thenReturn(existingConfig); + when(objectMapper.writeValueAsString(users)).thenReturn(userListJson); + + releasePackageService.updateReleaseMasterConfig(request); + + assertEquals("ADMIN_STAFF_SELECTED_USERS", existingConfig.getReleasePermissionType()); + assertEquals(userListJson, existingConfig.getUserList()); + assertTrue(existingConfig.getActive()); + + verify(releasePackageConfigRepository).save(existingConfig); + verify(releasePackageRepository).findAll(); + } + + @Test + public void shouldUpdatePermissionTypeForEachReleasePackage() { + Map requestBody = new HashMap<>(); + requestBody.put("releasePackageType", "ADMIN_ONLY"); + List releases = List.of(1L); + requestBody.put("releases", releases); + + ReleasePackage rp1 = new ReleasePackage(); + rp1.setPermissionType(ReleasePermissionType.ADMIN_ONLY); + + when(releasePackageRepository.findAllByReleasePackageIdIn(releases)).thenReturn(List.of(rp1)); + + releasePackageService.updateReleasePackagesPermission(requestBody); + + assertEquals(ReleasePermissionType.ADMIN_ONLY, rp1.getPermissionType()); + } + + @Test + public void shouldReturnMasterPermissionVisibilityDetails_WhenMasterConfigIsActive() { + ReleaseVersion releaseVersion = new ReleaseVersion(); + releaseVersion.setReleaseType("online"); + releaseVersion.setArchive(false); + + ReleasePackage releasePackage = new ReleasePackage(); + releasePackage.setPermissionType(ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS); + releasePackage.setReleaseVersions(Set.of(releaseVersion)); + + ReleasePackageConfig config = new ReleasePackageConfig(); + config.setReleaseType("online"); + config.setReleasePermissionType("ADMIN_STAFF"); + config.setActive(true); + + when(releasePackageConfigRepository.findByReleaseTypeIgnoreCase("online")).thenReturn(config); + + PermissionVisibilityResponse response = releasePackageService.getVisibilityDetails(releasePackage); + + assertTrue(response.isMasterPermission()); + assertEquals("ADMIN_STAFF", response.getPermissionType()); + assertEquals("online", response.getReleaseType()); + } + + @Test + public void shouldReturnPackageLevelPermission_WhenMasterConfigIsInactive() { + ReleaseVersion releaseVersion = new ReleaseVersion(); + releaseVersion.setReleaseType("offline"); + releaseVersion.setArchive(false); + + ReleasePackage releasePackage = new ReleasePackage(); + releasePackage.setPermissionType(ReleasePermissionType.NOT_SELECTED); + releasePackage.setReleaseVersions(Set.of(releaseVersion)); + + ReleasePackageConfig config = new ReleasePackageConfig(); + config.setReleaseType("offline"); + config.setReleasePermissionType("ADMIN_STAFF"); + config.setActive(false); + + when(releasePackageConfigRepository.findByReleaseTypeIgnoreCase("offline")).thenReturn(config); + + PermissionVisibilityResponse response = releasePackageService.getVisibilityDetails(releasePackage); + + assertFalse(response.isMasterPermission()); + assertEquals("NOT_SELECTED", response.getPermissionType()); + assertEquals("offline", response.getReleaseType()); + } + + @Test + public void shouldReturnPackagesForStaffWithProperPermissions() { + // Given + when(currentSecurityContext.isStaff()).thenReturn(true); + + ReleasePackage p1 = new ReleasePackage(); + p1.setMember(new Member("SE", 1)); + p1.setPermissionType(ReleasePermissionType.EVERYONE); + + ReleasePackage p2 = new ReleasePackage(); + p2.setMember(new Member("IN", 2)); + p2.setPermissionType(ReleasePermissionType.EVERYONE); + + List all = List.of(p1, p2); + List online = List.of(p1); + + ReleasePackageConfig grantedConfig = new ReleasePackageConfig(); + grantedConfig.setReleaseType("ONLINE"); + grantedConfig.setReleasePackageAccess("SE"); + grantedConfig.setReleasePermissionType("EVERYONE"); + grantedConfig.setUserList("[\"user1\"]"); + grantedConfig.setActive(true); + + ReleasePackageConfig notGrantedConfig = new ReleasePackageConfig(); + notGrantedConfig.setReleaseType("ONLINE"); + notGrantedConfig.setReleasePackageAccess("IN"); + notGrantedConfig.setReleasePermissionType("NOT_SELECTED"); + notGrantedConfig.setUserList("[]"); + notGrantedConfig.setActive(true); + + List granted = List.of(grantedConfig); + List notGranted = List.of(notGrantedConfig); + + when(releasePackageConfigRepository.findByReleasePermissionTypeNot("NOT_SELECTED")).thenReturn(granted); + when(releasePackageConfigRepository.findByReleasePermissionType("NOT_SELECTED")).thenReturn(notGranted); + + var result = releasePackageAccessService.getAccessiblePackagesForStaff(all, online, List.of(), List.of(), "SE"); + + assertEquals(1, result.size()); + assertTrue(result.contains(p1)); + } + + @Test + public void shouldReturnTrueWhenPermissionTypeIsAdminOnly() { + testPackage.setPermissionType(ReleasePermissionType.ADMIN_ONLY); + assertTrue(releasePackageAccessService.isAdminOnly(testPackage, config, masterConfig)); + } + + + @Test + public void shouldAllowAccessToSelectedUser() { + testPackage.setPermissionType(ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS); + + when(currentSecurityContext.getCurrentUserName()).thenReturn("user"); + when(userRepository.findByLoginIgnoreCase("user")).thenReturn(testUser); + when(releasePackageAccessRepository.existsByReleasePackageIdAndUserId(100L, 1L)).thenReturn(true); + + var result = releasePackageAccessService.handleUserAccess(testPackage, config, masterConfig, 100L); + assertEquals(HttpStatus.OK, result.getStatusCode()); + } + + @Test + public void shouldDenyAccessToNonSelectedUser() { + testPackage.setPermissionType(ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS); + + when(currentSecurityContext.getCurrentUserName()).thenReturn("user"); + when(userRepository.findByLoginIgnoreCase("user")).thenReturn(testUser); + when(releasePackageAccessRepository.existsByReleasePackageIdAndUserId(100L, 1L)).thenReturn(false); + + var result = releasePackageAccessService.handleUserAccess(testPackage, config, masterConfig, 100L); + assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode()); + } + +} diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/service/UserServiceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/service/UserServiceTest.java new file mode 100644 index 000000000..1cded1d63 --- /dev/null +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/service/UserServiceTest.java @@ -0,0 +1,170 @@ +package ca.intelliware.ihtsdo.mlds.service; + +import ca.intelliware.ihtsdo.mlds.domain.*; +import ca.intelliware.ihtsdo.mlds.repository.AffiliateRepository; +import ca.intelliware.ihtsdo.mlds.repository.ApplicationRepository; +import ca.intelliware.ihtsdo.mlds.repository.CommercialUsageRepository; +import ca.intelliware.ihtsdo.mlds.repository.MemberRepository; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.List; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class UserServiceTest { + @Mock + private ApplicationRepository applicationRepository; + + @Mock + private MemberRepository memberRepository; + + @Mock + private AffiliateRepository affiliateRepository; + + @Mock + private CommercialUsageRepository commercialUsageRepository; + + @InjectMocks + private UserService userService; + + Member swedenMember; + + @Before + public void setup() { + swedenMember = new Member("SE", 1); + swedenMember.setPendingApplication(10); + swedenMember.setInvoicesPending(0); + swedenMember.setUsageReports(5); + } + + + @Test + public void testRemovePendingApplication() { + Affiliate affiliate1 = withAffiliate(StandingState.APPLYING, AffiliateType.ACADEMIC, 10L); + Affiliate affiliate2 = withAffiliate(StandingState.APPLYING, AffiliateType.INDIVIDUAL, 11L); + + PrimaryApplication app1 = withExistingSwedishPrimaryApplication(1L, affiliate1); + PrimaryApplication app2 = withExistingSwedishPrimaryApplication(2L, affiliate2); + + when(applicationRepository.getAllApplication()).thenReturn(Arrays.asList(app1, app2)); + when(memberRepository.findMemberById(1L)).thenReturn(swedenMember); + + userService.removePendingApplication(); + + List resultAffiliate = Arrays.asList(10L,11L); + verify(applicationRepository, atLeastOnce()).getAllApplication(); + verify(applicationRepository, atLeastOnce()).updateLastProcessed(eq(resultAffiliate), any(Instant.class)); + } + + + @Test + public void testRemoveInvoicesPending_NoPendingInvoices() { + + when(memberRepository.findMemberById(1L)).thenReturn(swedenMember); + userService.removeInvoicesPending(); + + verify(affiliateRepository, never()).getIHTSDOPendingInvoices(); + verify(affiliateRepository, never()).updateLastProcessed(anyList(), any(Instant.class)); + verify(affiliateRepository, never()).updateAffiliateStandingStateAndDeactivationReason(anyLong(), any(),any()); + } + + @Test + public void testRemoveInvoicesPending_WithPendingInvoices() { + + swedenMember.setInvoicesPending(5); + when(memberRepository.findMemberById(1L)).thenReturn(swedenMember); + + + Affiliate affiliate1 = new Affiliate(10L); + affiliate1.setStandingState(StandingState.PENDING_INVOICE); + affiliate1.setCreated(Instant.now().minus(6, ChronoUnit.DAYS)); + + Affiliate affiliate2 = new Affiliate(11L); + affiliate2.setStandingState(StandingState.PENDING_INVOICE); + affiliate2.setCreated(Instant.now().minus(7, ChronoUnit.DAYS)); + + when(affiliateRepository.getIHTSDOPendingInvoices()).thenReturn(Arrays.asList(affiliate1, affiliate2)); + + + when(affiliateRepository.findActiveAffiliateIds(Arrays.asList(10L, 11L))).thenReturn(Arrays.asList(10L, 11L)); + + + Instant now = Instant.now(); + + userService.removeInvoicesPending(); + + verify(affiliateRepository).getIHTSDOPendingInvoices(); + verify(affiliateRepository).updateLastProcessed(eq(Arrays.asList(10L, 11L)), argThat(argument -> { + return argument.isAfter(now.minusSeconds(1)) && argument.isBefore(now.plusSeconds(1)); + })); + verify(affiliateRepository).updateAffiliateStandingStateAndDeactivationReason(10L,StandingState.DEREGISTERED, ReasonForDeactivation.AUTODEACTIVATION); + verify(affiliateRepository).updateAffiliateStandingStateAndDeactivationReason(11L,StandingState.DEREGISTERED, ReasonForDeactivation.AUTODEACTIVATION); + } + + + @Test + public void testRemoveUsageReports_WithValidData() throws Exception { + + Affiliate affiliate = new Affiliate(1L); + affiliate.setHomeMember(swedenMember); + + CommercialUsage usage = new CommercialUsage(); + + usage.setState(UsageReportState.NOT_SUBMITTED); + setField(usage, "commercialUsageId", 1L); + setField(usage, "affiliate", affiliate); + setField(usage, "created", Instant.now().minus(10, ChronoUnit.DAYS)); + + when(commercialUsageRepository.findByState()).thenReturn(List.of(usage)); + when(affiliateRepository.findById(1L)).thenReturn(java.util.Optional.of(affiliate)); + when(affiliateRepository.findActiveAffiliateIds(any())).thenReturn(List.of(1L)); + when(affiliateRepository.updateAffiliateStandingStateAndDeactivationReason(eq(1L),any(), any())).thenReturn(1); + + when(memberRepository.findMemberById(1L)).thenReturn(swedenMember); + + userService.removeUsageReports(); + + verify(commercialUsageRepository, times(1)).findByState(); + verify(affiliateRepository, times(1)).findById(1L); + verify(affiliateRepository, times(1)).updateAffiliateStandingStateAndDeactivationReason(1L,StandingState.DEREGISTERED, ReasonForDeactivation.AUTODEACTIVATION); + } + + private void setField(Object obj, String fieldName, Object value) throws Exception { + Field field = obj.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(obj, value); + } + + private Affiliate withAffiliate(StandingState existingStandingState, AffiliateType affiliateType, Long affiliateId) { + Affiliate affiliate = new Affiliate(affiliateId); + affiliate.setStandingState(existingStandingState); + affiliate.setAffiliateDetails(new AffiliateDetails()); + affiliate.setType(affiliateType); + Mockito.when(affiliateRepository.findByCreatorIgnoreCase(Mockito.anyString())).thenReturn(List.of(affiliate)); + return affiliate; + } + + private PrimaryApplication withExistingSwedishPrimaryApplication(long primaryApplicationId, Affiliate affiliate) { + PrimaryApplication primaryApplication = new PrimaryApplication(primaryApplicationId); + primaryApplication.setAffiliateDetails(new AffiliateDetails()); + primaryApplication.setMember(swedenMember); + primaryApplication.setAffiliate(affiliate); + primaryApplication.setApprovalState(ApprovalState.NOT_SUBMITTED); + primaryApplication.setSubmittedAt(Instant.now().minus(30, ChronoUnit.DAYS)); + CommercialUsage commercialUsage = new CommercialUsage(10L, affiliate); + commercialUsage.setType(affiliate.getType()); + primaryApplication.setCommercialUsage(commercialUsage); + return primaryApplication; + } +} diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AffiliateResource_AffiliatesFilter_Test.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AffiliateResource_AffiliatesFilter_Test.java index a99c2212f..8fe48dd78 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AffiliateResource_AffiliatesFilter_Test.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AffiliateResource_AffiliatesFilter_Test.java @@ -91,7 +91,7 @@ public void setup() { @Test public void getAffiliates() throws Exception { PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findAll(Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findAllByDeactivatedFalse(Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .accept(MediaType.APPLICATION_JSON_UTF8)) @@ -104,6 +104,7 @@ public void getAffiliates() throws Exception { private PageImpl withAffiliateResultForTestCreator() { Affiliate affiliate = createBlankAffiliate(); affiliate.setCreator("testCreator"); + affiliate.setDeactivated(false); PageImpl matches = new PageImpl<>(Arrays.asList(affiliate)); return matches; @@ -116,7 +117,7 @@ private ResultMatcher contentMatchesTestCreator() { @Test public void getAffiliatesCanReturnNoResults() throws Exception { PageImpl matches = new PageImpl<>(Lists.emptyList()); - when(affiliateRepository.findAll(Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findAllByDeactivatedFalse(Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .accept(MediaType.APPLICATION_JSON_UTF8)) @@ -128,7 +129,7 @@ public void getAffiliatesCanReturnNoResults() throws Exception { @Test public void getAffiliatesShouldSortByAffiliateIdByDefault() throws Exception { PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findAll(Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findAllByDeactivatedFalse(Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .accept(MediaType.APPLICATION_JSON_UTF8)) @@ -137,14 +138,14 @@ public void getAffiliatesShouldSortByAffiliateIdByDefault() throws Exception { .andExpect(contentMatchesTestCreator()); ArgumentCaptor argument = ArgumentCaptor.forClass(Pageable.class); - verify(affiliateRepository).findAll(argument.capture()); + verify(affiliateRepository).findAllByDeactivatedFalse(argument.capture()); assertThat(argument.getValue().getSort().toString(), is("affiliateId: ASC")); } @Test public void getAffiliatesShouldBeSortable() throws Exception { PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findAll(Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findAllByDeactivatedFalse(Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .param("$orderby", "member") @@ -154,14 +155,14 @@ public void getAffiliatesShouldBeSortable() throws Exception { .andExpect(contentMatchesTestCreator()); ArgumentCaptor argument = ArgumentCaptor.forClass(Pageable.class); - verify(affiliateRepository).findAll(argument.capture()); + verify(affiliateRepository).findAllByDeactivatedFalse(argument.capture()); assertThat(argument.getValue().getSort().toString(), is("homeMember.key: ASC,affiliateId: ASC")); } @Test public void getAffiliatesShouldBeSortableDescending() throws Exception { PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findAll(Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findAllByDeactivatedFalse(Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .param("$orderby", "member desc") @@ -171,14 +172,14 @@ public void getAffiliatesShouldBeSortableDescending() throws Exception { .andExpect(contentMatchesTestCreator()); ArgumentCaptor argument = ArgumentCaptor.forClass(Pageable.class); - verify(affiliateRepository).findAll(argument.capture()); + verify(affiliateRepository).findAllByDeactivatedFalse(argument.capture()); assertThat(argument.getValue().getSort().toString(), is("homeMember.key: DESC,affiliateId: ASC")); } @Test public void getAffiliatesShouldStartOnFirstPageByDefault() throws Exception { PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findAll(Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findAllByDeactivatedFalse(Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .accept(MediaType.APPLICATION_JSON_UTF8)) @@ -187,7 +188,7 @@ public void getAffiliatesShouldStartOnFirstPageByDefault() throws Exception { .andExpect(contentMatchesTestCreator()); ArgumentCaptor argument = ArgumentCaptor.forClass(Pageable.class); - verify(affiliateRepository).findAll(argument.capture()); + verify(affiliateRepository).findAllByDeactivatedFalse(argument.capture()); assertThat(argument.getValue().getPageNumber(), is(0)); assertThat(argument.getValue().getPageSize(), is(50)); assertThat(argument.getValue().getSort().toString(), is("affiliateId: ASC")); @@ -196,7 +197,7 @@ public void getAffiliatesShouldStartOnFirstPageByDefault() throws Exception { @Test public void getAffiliatesShouldAllowPaging() throws Exception { PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findAll(Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findAllByDeactivatedFalse(Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .param("$page", "2") @@ -207,7 +208,7 @@ public void getAffiliatesShouldAllowPaging() throws Exception { .andExpect(contentMatchesTestCreator()); ArgumentCaptor argument = ArgumentCaptor.forClass(Pageable.class); - verify(affiliateRepository).findAll(argument.capture()); + verify(affiliateRepository).findAllByDeactivatedFalse(argument.capture()); assertThat(argument.getValue().getPageNumber(), is(2)); assertThat(argument.getValue().getPageSize(), is(20)); } @@ -229,7 +230,7 @@ public void getAffiliatesShouldFilterByQuery() throws Exception { @Test public void getAffiliatesShouldFilterByStandingState() throws Exception { PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findByStandingState(Mockito.eq(StandingState.APPLYING), Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findByStandingStateAndDeactivatedFalse(Mockito.eq(StandingState.APPLYING), Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .param("$filter", "standingState eq 'APPLYING'") @@ -242,7 +243,7 @@ public void getAffiliatesShouldFilterByStandingState() throws Exception { @Test public void getAffiliatesShouldFilterByStandingStateNot() throws Exception { PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findByStandingStateNot(Mockito.eq(StandingState.APPLYING), Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findByStandingStateNotAndDeactivatedFalse(Mockito.eq(StandingState.APPLYING), Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .param("$filter", "not standingState eq 'APPLYING'") @@ -274,7 +275,7 @@ public void getAffiliatesShouldFilterByHomeMemberAndStandingState() throws Excep when(memberRepository.findOneByKey("SE")).thenReturn(member); PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findByHomeMemberAndStandingState(Mockito.eq(member), Mockito.eq(StandingState.APPLYING), Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findByHomeMemberAndStandingStateAndDeactivatedFalse(Mockito.eq(member), Mockito.eq(StandingState.APPLYING), Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .param("$filter", "homeMember eq 'SE'") @@ -291,7 +292,7 @@ public void getAffiliatesShouldFilterByHomeMemberAndStandingStateNot() throws Ex when(memberRepository.findOneByKey("SE")).thenReturn(member); PageImpl matches = withAffiliateResultForTestCreator(); - when(affiliateRepository.findByHomeMemberAndStandingStateNot(Mockito.eq(member), Mockito.eq(StandingState.APPLYING), Mockito.any(Pageable.class))).thenReturn(matches); + when(affiliateRepository.findByHomeMemberAndStandingStateNotAndDeactivatedFalse(Mockito.eq(member), Mockito.eq(StandingState.APPLYING), Mockito.any(Pageable.class))).thenReturn(matches); restUserMockMvc.perform(get(Routes.AFFILIATES) .param("$filter", "homeMember eq 'SE'") diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AtomFeedResourceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AtomFeedResourceTest.java new file mode 100644 index 000000000..11b7a9534 --- /dev/null +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AtomFeedResourceTest.java @@ -0,0 +1,86 @@ +package ca.intelliware.ihtsdo.mlds.web.rest; + +import ca.intelliware.ihtsdo.mlds.repository.ReleaseVersionRepository; +import ca.intelliware.ihtsdo.mlds.service.AtomFeedService; +import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.Value; + +import java.text.SimpleDateFormat; +import java.util.*; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class AtomFeedResourceTest { + @Mock + private ReleaseVersionRepository releaseVersionRepository; + + @InjectMocks + private AtomFeedService atomFeedService; + + @Value("${atom.feed.baseUrl}") + private String feedBaseUrl; + + @Value("${atom.feed.title}") + private String feedTitle; + + @Value("${atom.feed.link}") + private String feedLink; + + @Value("${atom.feed.UUID}") + private String feedUUID; + + @Value("${atom.feed.generator}") + private String feedGenerator; + + @Value("${atom.feed.profile}") + private String feedProfile; + + public SimpleDateFormat dateFormat; + + @BeforeEach + public void setup() { + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + } + + @Test + public void testGenerateAtomFeed() { + List mockAtomFeedData = createMockAtomFeedData(); + when(releaseVersionRepository.listAtomFeed()).thenReturn(mockAtomFeedData); + + String atomFeedXml = atomFeedService.generateAtomFeed(); + + assertAtomFeedContent(atomFeedXml); + } + + private List createMockAtomFeedData() { + List mockAtomFeedData = new ArrayList<>(); + mockAtomFeedData.add(new Object[]{ + "Title", "DownloadURL", "MemberOrgName", "MemberOrgURL", + "ContactEmail", "Id", "Copyrights", "2023-08-01 12:00:00.0", + "2023-07-31 12:00:00.0", "Summary", "ReleasePackageURI", + "VersionURI", "VersionDependentURI", "VersionDependentDerivativeURI", + "PackageId", "VersionId", "FileId", true, "Md5Hash", "FileSize", + "PackageType" + }); + return mockAtomFeedData; + } + + private void assertAtomFeedContent(String atomFeedXml) { + assertTrue(atomFeedXml.contains("")); + assertTrue(atomFeedXml.contains("" + feedTitle + "")); + assertTrue(atomFeedXml.contains("urn:uuid:" + feedUUID + "")); + assertTrue(atomFeedXml.contains("" + feedGenerator + "")); + assertTrue(atomFeedXml.contains("Title")); + assertTrue(atomFeedXml.contains("Summary")); + assertTrue(atomFeedXml.contains("")); + } + +} diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AuditResourceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AuditResourceTest.java index 1c80779f4..e34c8c28e 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AuditResourceTest.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/AuditResourceTest.java @@ -1,5 +1,7 @@ package ca.intelliware.ihtsdo.mlds.web.rest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -8,12 +10,18 @@ import java.text.SimpleDateFormat; import java.time.Instant; -import java.util.Arrays; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.*; +import ca.intelliware.ihtsdo.mlds.domain.PersistentAuditEvent; +import ca.intelliware.ihtsdo.mlds.repository.PersistenceAuditEventRepository; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.AuditEventRequestDTO; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.ReleaseFileCountDTO; import org.hamcrest.Matchers; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.boot.actuate.audit.AuditEvent; @@ -30,11 +38,22 @@ public class AuditResourceTest { @Mock private AuditEventService auditEventService; + @Mock + private PersistenceAuditEventRepository persistenceAuditEventRepository; private MockMvc restUserMockMvc; SecurityContextSetup securityContextSetup = new SecurityContextSetup(); + @InjectMocks + private AuditResource auditEventController; + + private LocalDate startDate; + private LocalDate endDate; + private Instant startInstant; + private Instant endInstant; + private Set expectedUserSet; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -44,6 +63,14 @@ public void setup() { auditResource.auditEventService = auditEventService; securityContextSetup.asAdmin(); + startDate = LocalDate.of(2023, 10, 1); + endDate = LocalDate.of(2023, 10, 31); + startInstant = startDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant(); + endInstant = endDate.atTime(23, 59, 59).atZone(ZoneId.systemDefault()).toInstant(); + + expectedUserSet = new HashSet<>(); + expectedUserSet.add("user1"); + expectedUserSet.add("user2"); this.restUserMockMvc = MockMvcBuilders .standaloneSetup(auditResource) @@ -112,7 +139,7 @@ public void findByFilterShouldFailForUnparseableDateRange() throws Exception { .andExpect(status().is4xxClientError()) ; } catch (NestedServletException e) { - Assert.assertEquals("Invalid format: \"NOT A DATE\"", e.getCause().getMessage()); + assertEquals("Invalid format: \"NOT A DATE\"", e.getCause().getMessage()); } } @@ -141,7 +168,91 @@ public void findByFilterShouldAllowFilteringByApplication() throws Exception { ; } - private AuditEvent createAuditEvent(String type) { - return new AuditEvent("testUser", type, "value1"); - } + @Test + public void findReleaseFileDownloadAuditData_ShouldReturnCorrectCounts() { + + AuditEventRequestDTO request = createAuditEventRequestDTO(); + request.setExcludeAdminAndStaff(false); + + Instant[] dateRange = new Instant[]{startInstant, endInstant}; + when(auditEventService.getStartEndInstant(request)).thenReturn(dateRange); + + List mockEvents = Arrays.asList( + createPersistentAuditEvent("user1", "FileA", "Version1", "PackageX"), + createPersistentAuditEvent("user2", "FileA", "Version1", "PackageX"), + createPersistentAuditEvent("user3", "FileB", "Version2", "PackageY") + ); + + when(auditEventService.getAuditEvents(false, startInstant, endInstant)).thenReturn(mockEvents); + when(auditEventService.filterDownloadEvents(mockEvents)).thenReturn(mockEvents); + + List response = auditEventController.findReleaseFileDownloadAuditData(request); + assertEquals(2, response.size()); + + assertAuditResponse(response, "FileA", "Version1", 2); + assertAuditResponse(response, "FileB", "Version2", 1); + } + + + @Test + public void findReleaseFileDownloadDataForCsv_ShouldReturnFilteredEvents() { + + AuditEventRequestDTO request = createAuditEventRequestDTO(); + request.setExcludeAdminAndStaff(true); + + Instant[] dateRange = new Instant[]{startInstant, endInstant}; + when(auditEventService.getStartEndInstant(request)).thenReturn(dateRange); + + List mockEvents = Arrays.asList( + createPersistentAuditEvent("user1", "FileA", "Version1", "PackageX"), + createPersistentAuditEvent("user2", "FileB", "Version2", "PackageY") + ); + + when(auditEventService.getAuditEvents(true, startInstant, endInstant)).thenReturn(mockEvents); + when(auditEventService.filterDownloadEvents(mockEvents)).thenReturn(mockEvents); + + List response = auditEventController.findReleaseFileDownloadDataForCsv(request); + + assertEquals(2, response.size()); + assertEquals("user1", response.get(0).getPrincipal()); + assertEquals("user2", response.get(1).getPrincipal()); + } + + + + private AuditEvent createAuditEvent(String type) { + return new AuditEvent("testUser", type, "value1"); + } + + + private AuditEventRequestDTO createAuditEventRequestDTO() { + AuditEventRequestDTO request = new AuditEventRequestDTO(); + request.setStartDate(startDate); + request.setEndDate(endDate); + return request; + } + + private PersistentAuditEvent createPersistentAuditEvent(String principal, String fileLabel, String versionName, String packageName) { + PersistentAuditEvent event = new PersistentAuditEvent(); + event.setPrincipal(principal); + event.setAuditEventType("RELEASE_FILE_DOWNLOADED"); + event.setAuditEventDate(startInstant); + + Map data = new HashMap<>(); + data.put("releaseFile.label", fileLabel); + data.put("releaseVersion.name", versionName); + data.put("releasePackage.name", packageName); + event.setData(data); + + return event; + } + + private void assertAuditResponse(List response, String expectedFileName, String expectedVersionName, int expectedCount) { + assertTrue(response.stream().anyMatch(dto -> + dto.getReleaseFileName().equals(expectedFileName) && + dto.getReleaseVersionName().equals(expectedVersionName) && + dto.getCount() == expectedCount)); + } + + } diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/CommercialUsageResource_GetUsageReports_Test.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/CommercialUsageResource_GetUsageReports_Test.java index 05d678ce4..6c90af38d 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/CommercialUsageResource_GetUsageReports_Test.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/CommercialUsageResource_GetUsageReports_Test.java @@ -1,15 +1,17 @@ package ca.intelliware.ihtsdo.mlds.web.rest; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -26,6 +28,9 @@ import ca.intelliware.ihtsdo.mlds.service.CommercialUsageAuthorizationChecker; import ca.intelliware.ihtsdo.mlds.service.CommercialUsageResetter; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.Optional; @RunWith(MockitoJUnitRunner.class) @@ -34,7 +39,7 @@ public class CommercialUsageResource_GetUsageReports_Test { AffiliateRepository affiliateRepository; @Mock - CommercialUsageAuthorizationChecker authorizationChecker; + CommercialUsageAuthorizationChecker authorizationChecker; @Mock CommercialUsageRepository commercialUsageRepository; @@ -45,7 +50,8 @@ public class CommercialUsageResource_GetUsageReports_Test { @Mock CommercialUsageResetter commercialUsageResetter; - CommercialUsageResource commercialUsageResource; + @InjectMocks + CommercialUsageResource commercialUsageResource; private MockMvc restCommercialUsageResource; @@ -65,9 +71,9 @@ public void setup() { .build(); } - @Test - public void getUsageReportsShouldReturn404ForUnknownAffiliate() throws Exception { - Mockito.when(affiliateRepository.findById(999L)).thenReturn(Optional.empty()); + @Test + public void getUsageReportsShouldReturn404ForUnknownAffiliate() throws Exception { + when(affiliateRepository.findById(999L)).thenReturn(Optional.empty()); restCommercialUsageResource.perform(MockMvcRequestBuilders.get(Routes.USAGE_REPORTS, 999L) .accept(MediaType.APPLICATION_JSON_UTF8)) @@ -84,7 +90,7 @@ public void getUsageReportsShouldReturnUsageReportsForAffiliate() throws Excepti CommercialUsageEntry commercialUsageEntry = new CommercialUsageEntry(4L, commercialUsage); commercialUsageEntry.setName("Test Name"); affiliate.addCommercialUsage(commercialUsage); - Mockito.when(affiliateRepository.findById(1L)).thenReturn(Optional.of(affiliate)); + when(affiliateRepository.findById(1L)).thenReturn(Optional.of(affiliate)); restCommercialUsageResource.perform(MockMvcRequestBuilders.get(Routes.USAGE_REPORTS, 1L) .accept(MediaType.APPLICATION_JSON_UTF8)) @@ -95,5 +101,24 @@ public void getUsageReportsShouldReturnUsageReportsForAffiliate() throws Excepti .andExpect(jsonPath("$[0].entries[0].name").value("Test Name")) ; - } + } + @Test + public void testReviewUsageReportCsv() { + Object[] sampleRow1 = { + 1, "MemberKey123", "US", "UK", "2024-01-01", "2024-12-31", "Active", + "2024-06-01", "AgreementTypeA", "John Doe", "Type-Subtype", "OrganizationName1", + "OrganizationType1", 5, 10, "Research", "Completed", "None", 2, 1, 3, 4, 20 + }; + Object[] sampleRow2 = { + 2, "MemberKey456", "CA", "FR", "2024-02-01", "2024-11-30", "Inactive", + "2024-06-10", "AgreementTypeB", "Jane Smith", "Type-Subtype", "OrganizationName2", + "OrganizationType2", 15, 25, "Education", "In Progress", "Training", 4, 3, 2, 1, 50 + }; + List sampleData = Arrays.asList(sampleRow1, sampleRow2); + when(commercialUsageRepository.findUsageReport()).thenReturn(sampleData); + Collection response = commercialUsageResource.reviewUsageReportCsv(); + assertEquals(sampleData.size(), response.size()); + assertTrue(response.containsAll(sampleData), "The response content should match the sample data"); + } + } diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/HealthCheckResourceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/HealthCheckResourceTest.java new file mode 100644 index 000000000..5316322e2 --- /dev/null +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/HealthCheckResourceTest.java @@ -0,0 +1,102 @@ +package ca.intelliware.ihtsdo.mlds.web.rest; + +import ca.intelliware.ihtsdo.mlds.config.metrics.DatabaseHealthCheckIndicator; +import ca.intelliware.ihtsdo.mlds.config.metrics.JavaMailHealthCheckIndicator; +import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class HealthCheckResourceTest { + @InjectMocks + private HealthCheckResource healthCheckResource; + + @Mock + private JavaMailHealthCheckIndicator javaMailHealthCheckIndicator; + + @Mock + private DatabaseHealthCheckIndicator databaseHealthCheckIndicator; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + } + @Test + public void testCheckHealth_BothServicesHealthy() { + // Arrange + when(javaMailHealthCheckIndicator.health()).thenReturn(Health.up().build()); + when(databaseHealthCheckIndicator.health()).thenReturn(Health.up().build()); + + // Act + Health health = healthCheckResource.checkHealth(); + + // Assert + assertEquals(Status.UP, health.getStatus()); + assertTrue(health.getDetails().containsKey("mail")); + assertTrue(health.getDetails().containsKey("database")); + + // Check that the details are also healthy + Health mailHealthDetail = (Health) health.getDetails().get("mail"); + Health databaseHealthDetail = (Health) health.getDetails().get("database"); + + assertEquals(Status.UP, mailHealthDetail.getStatus()); + assertEquals(Status.UP, databaseHealthDetail.getStatus()); + } + + + @Test + public void testCheckHealth_MailServiceUnhealthy() { + // Arrange + when(javaMailHealthCheckIndicator.health()).thenReturn(Health.down().build()); + when(databaseHealthCheckIndicator.health()).thenReturn(Health.up().build()); + + // Act + Health health = healthCheckResource.checkHealth(); + + // Assert + assertEquals(Status.DOWN, health.getStatus()); + assertTrue(health.getDetails().containsKey("mail")); + assertTrue(health.getDetails().containsKey("database")); + } + + @Test + public void testCheckHealth_DatabaseServiceUnhealthy() { + // Arrange + when(javaMailHealthCheckIndicator.health()).thenReturn(Health.up().build()); + when(databaseHealthCheckIndicator.health()).thenReturn(Health.down().build()); + + // Act + Health health = healthCheckResource.checkHealth(); + + // Assert + assertEquals(Status.DOWN, health.getStatus()); + assertTrue(health.getDetails().containsKey("mail")); + assertTrue(health.getDetails().containsKey("database")); + } + + @Test + public void testCheckHealth_BothServicesUnhealthy() { + // Arrange + when(javaMailHealthCheckIndicator.health()).thenReturn(Health.down().build()); + when(databaseHealthCheckIndicator.health()).thenReturn(Health.down().build()); + + // Act + Health health = healthCheckResource.checkHealth(); + + // Assert + assertEquals(Status.DOWN, health.getStatus()); + assertTrue(health.getDetails().containsKey("mail")); + assertTrue(health.getDetails().containsKey("database")); + } + +} diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/MemberResourceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/MemberResourceTest.java index 2efe508fa..895e941b6 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/MemberResourceTest.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/MemberResourceTest.java @@ -10,6 +10,7 @@ import java.util.Arrays; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.AutoDeactivationMemberDTO; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -30,33 +31,33 @@ public class MemberResourceTest { @Mock private MemberRepository memberRepository; @Mock private FileRepository fileRepository; @Mock private SessionService sessionService; - + private MockMvc restUserMockMvc; - + @Before public void setup() { MockitoAnnotations.initMocks(this); MemberResource memberResource = new MemberResource(); - + memberResource.memberRepository = memberRepository; memberResource.fileRepository = fileRepository; memberResource.sessionService = sessionService; - + this.restUserMockMvc = MockMvcBuilders .standaloneSetup(memberResource) .setMessageConverters(new MockMvcJacksonTestSupport().getConfiguredMessageConverters()) .build(); - + Mockito.when(memberRepository.findAll()).thenReturn(Arrays.asList(new Member("SE", 1), new Member("IHTSDO", 2))); } - + @Test public void getMembersShouldIncludeAllMembers() throws Exception { restUserMockMvc.perform(get(Routes.MEMBERS) .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()) .andExpect(content().string(Matchers.allOf(Matchers.containsString("SE"), Matchers.containsString("IHTSDO")))); - + } @Test @@ -66,22 +67,22 @@ public void getMembersShouldReturnPopulatedDTOs() throws Exception { .andExpect(status().isOk()) .andExpect(content().string(Matchers.containsString("\"memberId\":1,\"key\":\"SE\""))); } - + @Test public void postMembersNotificationsShouldUpdateStaffNotifications() throws Exception { Member member = new Member("SE", 1L); Mockito.when(memberRepository.findOneByKey("SE")).thenReturn(member); - + restUserMockMvc.perform(put(Routes.MEMBER_NOTIFICATIONS, "SE") .contentType(MediaType.APPLICATION_JSON_UTF8) .content("{ \"staffNotificationEmail\": \"staff@test.com\" }") .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - + Mockito.verify(memberRepository).save(member); assertThat(member.getStaffNotificationEmail(), equalTo("staff@test.com")); } - + @Test public void updateMemberShouldFailForUnknownMember() throws Exception { restUserMockMvc.perform(put(Routes.MEMBER, "ZZ") @@ -89,7 +90,7 @@ public void updateMemberShouldFailForUnknownMember() throws Exception { .content("{ \"staffNotificationEmail\": \"new@test.com\" }") .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isNotFound()); - + Mockito.verify(memberRepository, never()).save(Mockito.any(Member.class)); } @@ -99,18 +100,127 @@ public void updateMemberShouldUpdateFields() throws Exception { member.setStaffNotificationEmail("old@test.com"); member.setPromotePackages(false); Mockito.when(memberRepository.findOneByKey("SE")).thenReturn(member); - + restUserMockMvc.perform(put(Routes.MEMBER, "SE") .contentType(MediaType.APPLICATION_JSON_UTF8) .content("{ \"promotePackages\": true, \"staffNotificationEmail\": \"new@test.com\" }") .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - + Mockito.verify(memberRepository).save(member); - + assertThat(member.getStaffNotificationEmail(), equalTo("new@test.com")); assertThat(member.getPromotePackages(), equalTo(true)); } - - + + @Test + public void updateMemberFeedURLShouldUpdateFields() throws Exception { + Member member = new Member("SE", 1L); + member.setContactEmail("oldemail@example.com"); + member.setMemberOrgURL("http://old-url.com"); + member.setMemberOrgName("Old Org"); + Mockito.when(memberRepository.findOneByKey("SE")).thenReturn(member); + + restUserMockMvc.perform(put(Routes.MEMBER_FEED_URL, "SE") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content( "{ \"contactEmail\": \"newemail@example.com\", " + + "\"memberOrgURL\": \"http://new-url.com\", " + + "\"memberOrgName\": \"New Org\" }") + .accept(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + + Mockito.verify(memberRepository).save(member); + assertThat(member.getContactEmail(), equalTo("newemail@example.com")); + assertThat(member.getMemberOrgURL(), equalTo("http://new-url.com")); + assertThat(member.getMemberOrgName(), equalTo("New Org")); + } + + @Test + public void updateMemberFeedURLShouldReturnNotFoundForUnknownMember() throws Exception { + Mockito.when(memberRepository.findOneByKey("ZZ")).thenReturn(null); + + restUserMockMvc.perform(put(Routes.MEMBER_FEED_URL, "ZZ") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content("{ \"contactEmail\": \"newemail@example.com\", " + + "\"memberOrgURL\": \"http://new-url.com\", " + + "\"memberOrgName\": \"New Org\" }") + .accept(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isNotFound()); + Mockito.verify(memberRepository, never()).save(Mockito.any(Member.class)); + } + + + @Test + public void postAutoDeactivationMemberDetailsShouldUpdateMember() throws Exception { + String memberKey = "SE"; + Member member = new Member(memberKey, 1L); + + Mockito.when(memberRepository.findOneByKey(memberKey)).thenReturn(member); + + AutoDeactivationMemberDTO autoDeactivationDTO = new AutoDeactivationMemberDTO(); + autoDeactivationDTO.setInvoicesPending(5); + autoDeactivationDTO.setUsageReports(3); + autoDeactivationDTO.setPendingApplications(2); + + restUserMockMvc.perform(put(Routes.POST_MEMBER_AUTO_DEACTIVATION, memberKey) + .contentType(MediaType.APPLICATION_JSON) + .content("{ \"invoicesPending\": 5, \"usageReports\": 3, \"pendingApplications\": 2 }") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string(Matchers.containsString("Data Saved Successfully"))); + + Mockito.verify(memberRepository).save(member); + assertThat(member.getInvoicesPending(), equalTo(5)); + assertThat(member.getUsageReports(), equalTo(3)); + assertThat(member.getPendingApplication(), equalTo(2)); + } + + + @Test + public void postAutoDeactivationMemberDetailsShouldReturnNotFoundForUnknownMember() throws Exception { + String memberKey = "ZZ"; + Mockito.when(memberRepository.findOneByKey(memberKey)).thenReturn(null); + + restUserMockMvc.perform(put(Routes.POST_MEMBER_AUTO_DEACTIVATION, memberKey) + .contentType(MediaType.APPLICATION_JSON) + .content("{ \"invoicesPending\": 5, \"usageReports\": 3, \"pendingApplications\": 2 }") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andExpect(content().string(Matchers.containsString("Member not found"))); + + Mockito.verify(memberRepository, never()).save(Mockito.any(Member.class)); + } + + + @Test + public void getAutoDeactivationMemberDetailsShouldReturnMemberDetails() throws Exception { + String memberKey = "SE"; + Member member = new Member(memberKey, 1L); + member.setInvoicesPending(5); + member.setUsageReports(3); + member.setPendingApplication(2); + + Mockito.when(memberRepository.findOneByKey(memberKey)).thenReturn(member); + + restUserMockMvc.perform(get(Routes.MEMBER_AUTO_DEACTIVATION, memberKey) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().string("{\"pendingApplications\":2,\"usageReports\":3,\"invoicesPending\":5}")); + } + + @Test + public void getAutoDeactivationMemberDetailsShouldReturnNotFoundForUnknownMember() throws Exception { + + String memberKey = "ZZ"; + Mockito.when(memberRepository.findOneByKey(memberKey)).thenReturn(null); + + restUserMockMvc.perform(get(Routes.MEMBER_AUTO_DEACTIVATION, memberKey) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andExpect(content().string(Matchers.containsString(""))); + + Mockito.verify(memberRepository).findOneByKey(memberKey); + } + } diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseFilesResourceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseFilesResourceTest.java index 2ac71636a..72346b770 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseFilesResourceTest.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseFilesResourceTest.java @@ -12,14 +12,11 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.util.NestedServletException; -import ca.intelliware.ihtsdo.mlds.domain.Affiliate; import ca.intelliware.ihtsdo.mlds.domain.ReleaseFile; import ca.intelliware.ihtsdo.mlds.domain.ReleaseVersion; import ca.intelliware.ihtsdo.mlds.repository.ReleaseFileRepository; @@ -28,8 +25,7 @@ import ca.intelliware.ihtsdo.mlds.security.ihtsdo.CurrentSecurityContext; import ca.intelliware.ihtsdo.mlds.service.UserMembershipAccessor; -import java.util.ArrayList; -import java.util.List; +import java.io.FileNotFoundException; import java.util.Optional; public class ReleaseFilesResourceTest { @@ -126,4 +122,15 @@ public void downloadReleaseFileShouldFailIfCheckDenied() throws Exception { } } + @Test + public void testCheckIhtsdoFile() throws FileNotFoundException { + long releaseFileId = 1L; + ReleaseFile mockReleaseFile = new ReleaseFile(); + mockReleaseFile.setDownloadUrl("https://ire.published.release.ihtsdo.s3.amazonaws.com/international/SnomedCT_Release_INT_20140131.zip"); + Mockito.when(releaseFileRepository.findById(releaseFileId)) + .thenReturn(Optional.of(mockReleaseFile)); + Boolean result = releaseFilesResource.checkIhtsdoFile(1L, 1L, releaseFileId, null, null); + Assert.assertTrue("The download URL should contain 'ihtsdo'", result); + } + } diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleasePackagesResourceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleasePackagesResourceTest.java index 7e160ee46..23c640903 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleasePackagesResourceTest.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleasePackagesResourceTest.java @@ -1,36 +1,32 @@ package ca.intelliware.ihtsdo.mlds.web.rest; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import ca.intelliware.ihtsdo.mlds.domain.*; +import ca.intelliware.ihtsdo.mlds.repository.*; +import ca.intelliware.ihtsdo.mlds.service.ReleasePackageAccessService; +import ca.intelliware.ihtsdo.mlds.service.ReleasePackageService; +import ca.intelliware.ihtsdo.mlds.web.SessionService; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Mockito; +import org.mockito.*; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import ca.intelliware.ihtsdo.mlds.domain.Member; -import ca.intelliware.ihtsdo.mlds.domain.ReleasePackage; -import ca.intelliware.ihtsdo.mlds.domain.ReleaseVersion; -import ca.intelliware.ihtsdo.mlds.repository.MemberRepository; -import ca.intelliware.ihtsdo.mlds.repository.ReleaseFileRepository; -import ca.intelliware.ihtsdo.mlds.repository.ReleasePackageRepository; -import ca.intelliware.ihtsdo.mlds.repository.ReleaseVersionRepository; import ca.intelliware.ihtsdo.mlds.security.ihtsdo.CurrentSecurityContext; import ca.intelliware.ihtsdo.mlds.security.ihtsdo.SecurityContextSetup; import ca.intelliware.ihtsdo.mlds.service.ReleasePackagePrioritizer; import ca.intelliware.ihtsdo.mlds.service.UserMembershipAccessor; +import java.util.List; import java.util.Optional; @RunWith(MockitoJUnitRunner.class) @@ -64,17 +60,47 @@ public class ReleasePackagesResourceTest { @Mock ReleasePackagePrioritizer releasePackagePrioritizer; - @Captor + @Mock + ReleasePackageAccessRepository releasePackageAccessRepository; + + @Mock + private ReleasePackageConfigRepository releasePackageConfigRepository; + + @Mock + private ObjectMapper objectMapper; + + + @Captor ArgumentCaptor releasePacakgeCaptor; ReleasePackagesResource releasePackagesResource; + @Mock + ReleasePackageService releasePackageService; + SecurityContextSetup securityContextSetup = new SecurityContextSetup(); + @Mock + private ReleasePackageAccessService releasePackageAccessService; + + @Mock + private UserRepository userRepository; + + + + @Mock + private SessionService sessionService; @Before public void setup() { - releasePackagesResource = new ReleasePackagesResource(); + MockitoAnnotations.openMocks(this); // Initialize mocks + releasePackagesResource = new ReleasePackagesResource( + releasePackageAccessService, + userRepository, + releasePackageConfigRepository, + releasePackageService, + sessionService + ); releasePackagesResource.releasePackageRepository = releasePackageRepository; releasePackagesResource.authorizationChecker = authorizationChecker; @@ -82,8 +108,11 @@ public void setup() { releasePackagesResource.releasePackageAuditEvents = releasePackageAuditEvents; releasePackagesResource.userMembershipAccessor = userMembershipAccessor; releasePackagesResource.releasePackagePrioritizer = releasePackagePrioritizer; + releasePackagesResource.releasePackageAccessRepository = releasePackageAccessRepository; + releasePackagesResource.releasePackageService = releasePackageService; + - Mockito.when(userMembershipAccessor.getMemberAssociatedWithUser()).thenReturn(new Member("IHTSDO", 1)); + when(userMembershipAccessor.getMemberAssociatedWithUser()).thenReturn(new Member("IHTSDO", 1)); MockMvcJacksonTestSupport mockMvcJacksonTestSupport = new MockMvcJacksonTestSupport(); mockMvcJacksonTestSupport.memberRepository = memberRepository; @@ -101,13 +130,13 @@ public void testReleasePackageCreateSavesRecord() throws Exception { .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageRepository).save(Mockito.any(ReleasePackage.class)); + verify(releasePackageRepository).save(Mockito.any(ReleasePackage.class)); } @Test public void testReleasePackageCreateIgnoresBodyMemberAndAttachesPackageToUserMember() throws Exception { Member userMember = new Member("SE", 1); - Mockito.when(userMembershipAccessor.getMemberAssociatedWithUser()).thenReturn(userMember); + when(userMembershipAccessor.getMemberAssociatedWithUser()).thenReturn(userMember); restReleasePackagesResource.perform(MockMvcRequestBuilders.post(Routes.RELEASE_PACKAGES) .contentType(MediaType.APPLICATION_JSON_UTF8) @@ -115,7 +144,7 @@ public void testReleasePackageCreateIgnoresBodyMemberAndAttachesPackageToUserMem .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageRepository).save(releasePacakgeCaptor.capture()); + verify(releasePackageRepository).save(releasePacakgeCaptor.capture()); assertEquals(releasePacakgeCaptor.getValue().getMember(), userMember); } @@ -123,7 +152,7 @@ public void testReleasePackageCreateIgnoresBodyMemberAndAttachesPackageToUserMem public void testReleasePackageCreateUsesBodyMemberForAdmin() throws Exception { Member userMember = new Member("SE", 1); Member bodyMember = new Member("DK", 2); - Mockito.when(memberRepository.findOneByKey("DK")).thenReturn(bodyMember); + when(memberRepository.findOneByKey("DK")).thenReturn(bodyMember); // Mockito.when(userMembershipAccessor.getMemberAssociatedWithUser()).thenReturn(userMember); securityContextSetup.asAdmin(); @@ -133,14 +162,14 @@ public void testReleasePackageCreateUsesBodyMemberForAdmin() throws Exception { .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageRepository).save(releasePacakgeCaptor.capture()); + verify(releasePackageRepository).save(releasePacakgeCaptor.capture()); assertEquals(bodyMember, releasePacakgeCaptor.getValue().getMember()); } @Test public void testReleasePackageCreateDefaultsMemberForAdminIfNotInBody() throws Exception { Member userMember = new Member("XX", 1); - Mockito.when(userMembershipAccessor.getMemberAssociatedWithUser()).thenReturn(userMember); + when(userMembershipAccessor.getMemberAssociatedWithUser()).thenReturn(userMember); securityContextSetup.asAdmin(); restReleasePackagesResource.perform(MockMvcRequestBuilders.post(Routes.RELEASE_PACKAGES) @@ -149,7 +178,7 @@ public void testReleasePackageCreateDefaultsMemberForAdminIfNotInBody() throws E .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageRepository).save(releasePacakgeCaptor.capture()); + verify(releasePackageRepository).save(releasePacakgeCaptor.capture()); assertEquals(releasePacakgeCaptor.getValue().getMember(), userMember); } @@ -161,7 +190,7 @@ public void testReleasePackageLogsAuditEvent() throws Exception { .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageAuditEvents).logCreationOf(Mockito.any(ReleasePackage.class)); + verify(releasePackageAuditEvents).logCreationOf(Mockito.any(ReleasePackage.class)); } @Test @@ -172,15 +201,15 @@ public void testReleasePackageCreateShouldInitializePackagesPriorityToEndOfList( .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageRepository).save(Mockito.any(ReleasePackage.class)); + verify(releasePackageRepository).save(Mockito.any(ReleasePackage.class)); - Mockito.verify(releasePackagePrioritizer).prioritize(Mockito.any(ReleasePackage.class), Mockito.eq(ReleasePackagePrioritizer.END_PRIORITY)); + verify(releasePackagePrioritizer).prioritize(Mockito.any(ReleasePackage.class), Mockito.eq(ReleasePackagePrioritizer.END_PRIORITY)); } @Test public void testReleasePackageUpdateFailsForUnknownId() throws Exception { - Mockito.when(releasePackageRepository.findById(999L)).thenReturn(Optional.empty()); + when(releasePackageRepository.findById(999L)).thenReturn(Optional.empty()); restReleasePackagesResource.perform(MockMvcRequestBuilders.put(Routes.RELEASE_PACKAGE, 999L) .contentType(MediaType.APPLICATION_JSON_UTF8) @@ -188,14 +217,14 @@ public void testReleasePackageUpdateFailsForUnknownId() throws Exception { .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isNotFound()); - Mockito.verify(releasePackageRepository, Mockito.never()).save(Mockito.any(ReleasePackage.class)); + verify(releasePackageRepository, Mockito.never()).save(Mockito.any(ReleasePackage.class)); } @Test public void testReleasePackageUpdateShouldSave() throws Exception { ReleasePackage releasePackage = new ReleasePackage(); - Mockito.when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); + when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); restReleasePackagesResource.perform(MockMvcRequestBuilders.put(Routes.RELEASE_PACKAGE, 1L) .contentType(MediaType.APPLICATION_JSON_UTF8) @@ -203,7 +232,7 @@ public void testReleasePackageUpdateShouldSave() throws Exception { .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageRepository).save(Mockito.any(ReleasePackage.class)); + verify(releasePackageRepository).save(Mockito.any(ReleasePackage.class)); } @Test @@ -213,7 +242,7 @@ public void testReleasePackageUpdateShouldOnlyCopySubsetOfFields() throws Except releasePackage.setDescription("originalDescription"); releasePackage.setCreatedBy("originalCreatedBy"); - Mockito.when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); + when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); restReleasePackagesResource.perform(MockMvcRequestBuilders.put(Routes.RELEASE_PACKAGE, 1L) .contentType(MediaType.APPLICATION_JSON_UTF8) @@ -222,7 +251,7 @@ public void testReleasePackageUpdateShouldOnlyCopySubsetOfFields() throws Except .andExpect(status().isOk()); ArgumentCaptor savedReleasePackage = ArgumentCaptor.forClass(ReleasePackage.class); - Mockito.verify(releasePackageRepository).save(savedReleasePackage.capture()); + verify(releasePackageRepository).save(savedReleasePackage.capture()); Assert.assertEquals("newName", savedReleasePackage.getValue().getName()); Assert.assertEquals("newDescription", savedReleasePackage.getValue().getDescription()); @@ -238,7 +267,7 @@ public void testReleasePackageUpdateShouldReorderMemberPackagesWhenPriorityChang releasePackage.setCreatedBy("originalCreatedBy"); releasePackage.setPriority(5); - Mockito.when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); + when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); restReleasePackagesResource.perform(MockMvcRequestBuilders.put(Routes.RELEASE_PACKAGE, 1L) .contentType(MediaType.APPLICATION_JSON_UTF8) @@ -247,9 +276,9 @@ public void testReleasePackageUpdateShouldReorderMemberPackagesWhenPriorityChang .andExpect(status().isOk()); ArgumentCaptor savedReleasePackage = ArgumentCaptor.forClass(ReleasePackage.class); - Mockito.verify(releasePackageRepository).save(savedReleasePackage.capture()); + verify(releasePackageRepository).save(savedReleasePackage.capture()); - Mockito.verify(releasePackagePrioritizer).prioritize(releasePackage, 9); + verify(releasePackagePrioritizer).prioritize(releasePackage, 9); } @Test @@ -259,14 +288,14 @@ public void testReleasePackageDeleteShouldFailForActiveVersion() throws Exceptio activeVersion.setOnline(true); releasePackage.addReleaseVersion(activeVersion); - Mockito.when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); + when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); restReleasePackagesResource.perform(MockMvcRequestBuilders.delete(Routes.RELEASE_PACKAGE, 1L) .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isConflict()); - Mockito.verify(releasePackageRepository, Mockito.never()).delete(Mockito.any(ReleasePackage.class)); + verify(releasePackageRepository, Mockito.never()).delete(Mockito.any(ReleasePackage.class)); } @Test @@ -276,28 +305,65 @@ public void testReleasePackageDeleteShouldSucceedForInactiveVersion() throws Exc inactiveVersion.setOnline(false); releasePackage.addReleaseVersion(inactiveVersion); - Mockito.when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); + when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); restReleasePackagesResource.perform(MockMvcRequestBuilders.delete(Routes.RELEASE_PACKAGE, 1L) .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageRepository).delete(Mockito.any(ReleasePackage.class)); + verify(releasePackageRepository).delete(Mockito.any(ReleasePackage.class)); } @Test public void testReleasePackageDeleteLogsAuditEvent() throws Exception { ReleasePackage releasePackage = new ReleasePackage(); - Mockito.when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); + when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); restReleasePackagesResource.perform(MockMvcRequestBuilders.delete(Routes.RELEASE_PACKAGE, 1L) .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); - Mockito.verify(releasePackageAuditEvents).logDeletionOf(Mockito.any(ReleasePackage.class)); + verify(releasePackageAuditEvents).logDeletionOf(Mockito.any(ReleasePackage.class)); } + @Test + public void testUpdateReleasePackageType_AdminStaffSelectedUsers_ShouldSaveAccess() throws Exception { + + ReleasePackage releasePackage = Mockito.spy(new ReleasePackage()); + when(releasePackage.getReleasePackageId()).thenReturn(1L); + when(releasePackageRepository.findById(1L)).thenReturn(Optional.of(releasePackage)); + + Mockito.doNothing().when(authorizationChecker).checkCanEditReleasePackage(Mockito.any()); + + String requestBody = """ + { + "releasePackageType": "ADMIN_STAFF_SELECTED_USERS", + "users": ["100", "101"] + } + """; + + restReleasePackagesResource.perform(MockMvcRequestBuilders.put(Routes.RELEASE_PACKAGE_PERMISSION, 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + assertEquals(ReleasePermissionType.ADMIN_STAFF_SELECTED_USERS, releasePackage.getPermissionType()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ReleasePackageAccess.class); + verify(releasePackageAccessRepository, times(2)).save(captor.capture()); + + List savedUserIds = captor.getAllValues().stream() + .map(ReleasePackageAccess::getUserId) + .toList(); + + assertTrue(savedUserIds.contains(100L)); + assertTrue(savedUserIds.contains(101L)); + + verify(releasePackageRepository).save(releasePackage); + } + } diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseVersionsResourceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseVersionsResourceTest.java index 460c96d4c..9d98e4ffa 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseVersionsResourceTest.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/ReleaseVersionsResourceTest.java @@ -1,9 +1,11 @@ package ca.intelliware.ihtsdo.mlds.web.rest; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; + +import ca.intelliware.ihtsdo.mlds.web.rest.dto.ReleaseVersionCheckViewDTO; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.hamcrest.Matchers; @@ -27,6 +29,8 @@ import ca.intelliware.ihtsdo.mlds.repository.ReleaseVersionRepository; import ca.intelliware.ihtsdo.mlds.security.ihtsdo.CurrentSecurityContext; +import java.util.Arrays; +import java.util.List; import java.util.Optional; public class ReleaseVersionsResourceTest { @@ -257,4 +261,70 @@ public void testUpdateArchiveReleaseVersionNotFound() { verify(releaseVersionRepository).findById(1L); verify(authorizationChecker, never()).checkCanEditReleasePackage(any()); } + + @Test + public void testCheckVersionDependency_ReturnsTrue() { + long releaseVersionId = 1L; + ReleaseVersion releaseVersion = new ReleaseVersion(); + releaseVersion.setVersionURI("someVersionURI"); + + when(releaseVersionRepository.findById(releaseVersionId)).thenReturn(Optional.of(releaseVersion)); + when(releaseVersionRepository.checkDependent("someVersionURI")).thenReturn(1L); + + Boolean result = releaseVersionsResource.checkVersionDependency(releaseVersionId); + assertTrue(result); + } + + @Test + public void shouldReturnDependentVersionsNames() { + long releaseVersionId = 1L; + String versionURI = "someVersionURI"; + + ReleaseVersion releaseVersion = new ReleaseVersion(); + releaseVersion.setVersionURI(versionURI); + + ReleaseVersionCheckViewDTO version1 = Mockito.mock(ReleaseVersionCheckViewDTO.class); + when(version1.getReleaseVersionName()).thenReturn("Version 1"); + when(version1.getReleasePackageId()).thenReturn(100L); + + ReleaseVersionCheckViewDTO version2 = Mockito.mock(ReleaseVersionCheckViewDTO.class); + when(version2.getReleaseVersionName()).thenReturn("Version 2"); + when(version2.getReleasePackageId()).thenReturn(101L); + + when(releaseVersionRepository.findById(releaseVersionId)).thenReturn(Optional.of(releaseVersion)); + when(releaseVersionRepository.getDependentVersionNames(versionURI)).thenReturn(Arrays.asList(version1, version2)); + + List result = releaseVersionsResource.getDependentVersionsNames(releaseVersionId); + + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Version 1", result.get(0).getReleaseVersionName()); + assertEquals("Version 2", result.get(1).getReleaseVersionName()); + } + + + + + @Test + public void testCheckVersionDependency_ReturnsFalseWhenNoDependency() { + long releaseVersionId = 1L; + ReleaseVersion releaseVersion = new ReleaseVersion(); + releaseVersion.setVersionURI("someVersionURI"); + + when(releaseVersionRepository.findById(releaseVersionId)).thenReturn(Optional.of(releaseVersion)); + when(releaseVersionRepository.checkDependent("someVersionURI")).thenReturn(0L); + + Boolean result = releaseVersionsResource.checkVersionDependency(releaseVersionId); + assertFalse(result); + } + + @Test + public void testCheckVersionDependency_ReturnsFalseWhenNotFound() { + long releaseVersionId = 1L; + + when(releaseVersionRepository.findById(releaseVersionId)).thenReturn(Optional.empty()); + + Boolean result = releaseVersionsResource.checkVersionDependency(releaseVersionId); + assertFalse(result); + } } diff --git a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/UserResourceTest.java b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/UserResourceTest.java index 15bd76c9b..5c0ae4703 100644 --- a/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/UserResourceTest.java +++ b/src/test/java/ca/intelliware/ihtsdo/mlds/web/rest/UserResourceTest.java @@ -1,27 +1,42 @@ package ca.intelliware.ihtsdo.mlds.web.rest; +import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import ca.intelliware.ihtsdo.mlds.config.MySqlTestContainerTest; +import ca.intelliware.ihtsdo.mlds.domain.Affiliate; +import ca.intelliware.ihtsdo.mlds.domain.Member; +import ca.intelliware.ihtsdo.mlds.domain.User; +import ca.intelliware.ihtsdo.mlds.repository.AffiliateRepository; +import ca.intelliware.ihtsdo.mlds.repository.MemberRepository; +import ca.intelliware.ihtsdo.mlds.service.UserService; +import ca.intelliware.ihtsdo.mlds.web.rest.dto.AffiliateDetailsResponseDTO; import jakarta.transaction.Transactional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import ca.intelliware.ihtsdo.mlds.repository.UserRepository; +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.UUID; + /** * Test class for the UserResource REST controller. @@ -37,20 +52,216 @@ public class UserResourceTest extends MySqlTestContainerTest { @Autowired private UserRepository userRepository; + @Autowired + private AffiliateRepository affiliateRepository; + @Autowired public MemberRepository memberRepository; + @Mock + public UserService userService; private MockMvc restUserMockMvc; + UserResource userResource; + @Before public void setup() { - UserResource userResource = new UserResource(); - ReflectionTestUtils.setField(userResource, "userRepository", userRepository); + userResource = new UserResource(userService,affiliateRepository); + userResource.userRepository = userRepository; + userResource.userService = userService; + userResource.affiliateRepository = affiliateRepository; this.restUserMockMvc = MockMvcBuilders.standaloneSetup(userResource).build(); } @Test public void testGetUnknownUser() throws Exception { restUserMockMvc.perform(get("/api/users/unknown") - .accept(MediaType.APPLICATION_JSON_UTF8)) + .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } + + @Test + public void getUserDetailsShouldReturnOk_WhenNoAffiliatesFound() throws Exception { + String login = "test@test.com"; + Long affiliateDetailsId = 1L; + + User user = new User(); + user.setLogin(login); + user.setUserId(1L); + + AffiliateDetailsResponseDTO responseDTO = new AffiliateDetailsResponseDTO(user, null, Collections.emptyList()); + + when(userService.getAffiliateDetails(login)).thenReturn(responseDTO); + + restUserMockMvc.perform(post("/api/getUserDetails") + .param("login", login) + .param("affiliateDetailsId", affiliateDetailsId.toString()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.user.login").value(login)) + .andExpect(jsonPath("$.affiliateDetails").isEmpty()) + .andExpect(jsonPath("$.affiliate").isEmpty()); + + Mockito.verify(userService, Mockito.times(1)).getAffiliateDetails(login); + } + + + @Test + public void getUserDetailsShouldReturnOk_WhenUserNotFound() throws Exception { + String login = "nonexistent@test.com"; + Long affiliateDetailsId = 1L; + + AffiliateDetailsResponseDTO responseDTO = new AffiliateDetailsResponseDTO(null, null, Collections.emptyList()); + + when(userService.getAffiliateDetails(login)).thenReturn(responseDTO); + + restUserMockMvc.perform(post("/api/getUserDetails") + .param("login", login) + .param("affiliateDetailsId", affiliateDetailsId.toString()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.user").isEmpty()) + .andExpect(jsonPath("$.affiliateDetails").isEmpty()) + .andExpect(jsonPath("$.affiliate").isEmpty()); + + Mockito.verify(userService, Mockito.times(1)).getAffiliateDetails(login); + } + + + @Test + public void updatePrimaryEmailShouldReturnOk_WhenEmailIsUpdatedSuccessfully() throws Exception { + String login = "test@test.com"; + String updatedEmail = "newemail@test.com"; + + // Mock successful execution (no exception thrown) + doNothing().when(userService).updatePrimaryEmail(login, updatedEmail); + + restUserMockMvc.perform(post("/api/updatePrimaryEmail") + .param("login", login) + .param("updatedEmail", updatedEmail) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string("Primary email updated successfully")); + + Mockito.verify(userService, Mockito.times(1)).updatePrimaryEmail(login, updatedEmail); + } + + @Test + public void updatePrimaryEmailShouldReturnNotFound_WhenUserDoesNotExist() throws Exception { + String login = "nonexistent@test.com"; + String updatedEmail = "testupdate@test.com"; + + // Mock userService to throw NoSuchElementException + doThrow(new NoSuchElementException("User or related data not found")) + .when(userService).updatePrimaryEmail(login, updatedEmail); + + restUserMockMvc.perform(post("/api/updatePrimaryEmail") + .param("login", login) + .param("updatedEmail", updatedEmail) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andExpect(content().string("User or related data not found")); + + Mockito.verify(userService, Mockito.times(1)).updatePrimaryEmail(login, updatedEmail); + } + + @Test + public void updatePrimaryEmailShouldReturnInternalServerError_WhenUnexpectedErrorOccurs() throws Exception { + String login = "test@test.com"; + String updatedEmail = "testupdate@test.com"; + + // Mock userService to throw a generic exception + doThrow(new RuntimeException("Unexpected error")) + .when(userService).updatePrimaryEmail(login, updatedEmail); + + restUserMockMvc.perform(post("/api/updatePrimaryEmail") + .param("login", login) + .param("updatedEmail", updatedEmail) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isInternalServerError()) + .andExpect(content().string("An error occurred while updating the email")); + + Mockito.verify(userService, Mockito.times(1)).updatePrimaryEmail(login, updatedEmail); + } + + private Member createUniqueMember() { + String uniqueKey = "SE-" + UUID.randomUUID(); + Member member = new Member(uniqueKey, 1); + member.setName("Sweden " + UUID.randomUUID()); + member.setPromotePackages(false); + return memberRepository.save(member); + } + + private Affiliate createAffiliateWithCreator(String creatorLogin, Member member) { + Affiliate affiliate = new Affiliate(); + affiliate.setCreator(creatorLogin); + affiliate.setHomeMember(member); + return affiliateRepository.save(affiliate); + } + + private User createUser(String login, boolean acceptNotifications, String unsubscribeKey) { + User user = new User(); + user.setLogin(login); + user.setAcceptNotifications(acceptNotifications); + user.setUnsubscribeKey(unsubscribeKey); + return userRepository.save(user); + } + @Test + public void unsubscribeOnceShouldReturnOk_WhenValidRequest() throws Exception { + String key = "valid-key"; + Member member = createUniqueMember(); + Affiliate affiliate = createAffiliateWithCreator("creatorLogin", member); + createUser("creatorLogin", true, key); + + restUserMockMvc.perform(post("/api/unsubscribenotification/" + affiliate.getAffiliateId() + "/" + key) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string("You have successfully unsubscribed.")); + } + + @Test + public void unsubscribeOnceShouldReturnUnauthorized_WhenKeyMismatch() throws Exception { + Member member = createUniqueMember(); + Affiliate affiliate = createAffiliateWithCreator("creatorLogin", member); + createUser("creatorLogin", true, "correct-key"); + + restUserMockMvc.perform(post("/api/unsubscribenotification/" + affiliate.getAffiliateId() + "/wrong-key") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isUnauthorized()) + .andExpect(content().string("Invalid or expired unsubscribe link.")); + } + + @Test + public void unsubscribeOnceShouldReturnGone_WhenAlreadyUnsubscribed() throws Exception { + String key = "some-key"; + Member member = createUniqueMember(); + Affiliate affiliate = createAffiliateWithCreator("creatorLogin", member); + createUser("creatorLogin", false, key); + + restUserMockMvc.perform(post("/api/unsubscribenotification/" + affiliate.getAffiliateId() + "/" + key) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isGone()) + .andExpect(content().string("This unsubscribe link has already been used.")); + } + + @Test + public void unsubscribeOnceShouldReturnNotFound_WhenUserNotFound() throws Exception { + String key = "some-key"; + Member member = createUniqueMember(); + Affiliate affiliate = createAffiliateWithCreator("nonexistentUser", member); + + restUserMockMvc.perform(post("/api/unsubscribenotification/" + affiliate.getAffiliateId() + "/" + key) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andExpect(content().string("User not found.")); + } + + @Test + public void unsubscribeOnceShouldReturnNotFound_WhenAffiliateNotFound() throws Exception { + restUserMockMvc.perform(post("/api/unsubscribenotification/9999/any-key") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andExpect(content().string("Affiliate not found.")); + } + + + }