diff --git a/examples/crosslisting.json b/examples/crosslisting.json
index 4b6a795..61ac83f 100644
--- a/examples/crosslisting.json
+++ b/examples/crosslisting.json
@@ -23,6 +23,7 @@
"custom_fields": {
"instructure_membership_roles": "$com.Instructure.membership.roles",
"canvas_course_id": "$Canvas.course.id",
- "canvas_user_login_id": "$Canvas.user.loginId"
+ "canvas_user_login_id": "$Canvas.user.loginId",
+ "is_crosslist_tool": "true"
}
}
\ No newline at end of file
diff --git a/examples/crosslisting-lookup.json b/examples/decrosslisting.json
similarity index 75%
rename from examples/crosslisting-lookup.json
rename to examples/decrosslisting.json
index df5380f..002ffb7 100644
--- a/examples/crosslisting-lookup.json
+++ b/examples/decrosslisting.json
@@ -1,7 +1,7 @@
{
- "title": "Cross-listing Lookup Parent",
- "description": "For Looking up a course's parent course if cross-listed in Canvas.",
- "oidc_initiation_url": "http://localhost:8080/lti/login_initiation/lms_lti_crosslisting_lookup",
+ "title": "De-Cross-listing Sections",
+ "description": "For searching for parent courses and de-cross-listing sections quickly.",
+ "oidc_initiation_url": "http://localhost:8080/lti/login_initiation/lms_lti_decrosslisting",
"target_link_uri": "http://localhost:8080/app/lookup-launch",
"extensions": [
{
@@ -12,7 +12,7 @@
"placements": [
{
"enabled": true,
- "placement": "account_navigation",
+ "placement": "course_navigation",
"message_type": "LtiResourceLinkRequest"
}
]
diff --git a/pom.xml b/pom.xml
index 8b37e67..dafe1e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,7 @@
5.2.434.82021.0.8
+ 1.8.00.473.11.0
@@ -148,6 +149,16 @@
${jsoup.version}test
+
+ org.springdoc
+ springdoc-openapi-ui
+ ${springdoc-openapi-ui.version}
+
+
+ org.springdoc
+ springdoc-openapi-data-rest
+ ${springdoc-openapi-ui.version}
+ org.springframework.bootspring-boot-starter-actuator
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/WebApplication.java b/src/main/java/edu/iu/uits/lms/crosslist/WebApplication.java
index 6e6a683..22e4ec6 100644
--- a/src/main/java/edu/iu/uits/lms/crosslist/WebApplication.java
+++ b/src/main/java/edu/iu/uits/lms/crosslist/WebApplication.java
@@ -59,7 +59,7 @@
@Slf4j
@EnableCookieFilter(ignoredRequestPatterns = "/rest/**")
@EnableRedisConfiguration
-@EnableLtiClient(toolKeys = {"lms_lti_crosslisting", "lms_lti_crosslisting_lookup"})
+@EnableLtiClient(toolKeys = {"lms_lti_crosslisting", "lms_lti_decrosslisting"})
@EnableCanvasClient
@EnableIuOnlyClient
@EnableConfigurationProperties(GitRepositoryState.class)
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/config/CustomRoleMapper.java b/src/main/java/edu/iu/uits/lms/crosslist/config/CustomRoleMapper.java
index b9271fb..ef62ee8 100644
--- a/src/main/java/edu/iu/uits/lms/crosslist/config/CustomRoleMapper.java
+++ b/src/main/java/edu/iu/uits/lms/crosslist/config/CustomRoleMapper.java
@@ -33,12 +33,19 @@
* #L%
*/
+import edu.iu.uits.lms.crosslist.model.DecrosslistUser;
+import edu.iu.uits.lms.crosslist.repository.DecrosslistUserRepository;
import edu.iu.uits.lms.lti.LTIConstants;
import edu.iu.uits.lms.lti.repository.DefaultInstructorRoleRepository;
import edu.iu.uits.lms.lti.service.LmsDefaultGrantedAuthoritiesMapper;
+import edu.iu.uits.lms.lti.service.OidcTokenUtils;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
@Slf4j
@@ -46,9 +53,12 @@ public class CustomRoleMapper extends LmsDefaultGrantedAuthoritiesMapper {
private ToolConfig toolConfig;
- public CustomRoleMapper(DefaultInstructorRoleRepository defaultInstructorRoleRepository, ToolConfig toolConfig) {
+ private DecrosslistUserRepository decrosslistUserRepository;
+
+ public CustomRoleMapper(DefaultInstructorRoleRepository defaultInstructorRoleRepository, ToolConfig toolConfig, DecrosslistUserRepository decrosslistUserRepository) {
super(defaultInstructorRoleRepository);
this.toolConfig = toolConfig;
+ this.decrosslistUserRepository = decrosslistUserRepository;
}
@Override
@@ -66,4 +76,42 @@ protected String returnEquivalentAuthority(String[] userRoles, List inst
//Then do normal stuff
return super.returnEquivalentAuthority(userRoles, instructorRoles);
}
+
+ @Override
+ public Collection extends GrantedAuthority> mapAuthorities(Collection extends GrantedAuthority> authorities) {
+ List remappedAuthorities = new ArrayList<>();
+ remappedAuthorities.addAll(authorities);
+ for (GrantedAuthority authority : authorities) {
+ OidcUserAuthority userAuth = (OidcUserAuthority) authority;
+ OidcTokenUtils oidcTokenUtils = new OidcTokenUtils(userAuth.getAttributes());
+ log.debug("LTI Claims: {}", userAuth.getAttributes());
+
+ if (Boolean.parseBoolean(oidcTokenUtils.getCustomValue("is_crosslist_tool"))) {
+ log.debug("CustomRoleMapper: Crosslist tool");
+ // Use the legit roles for crosslisting, which is less restrictive than the decrosslist tool
+ return super.mapAuthorities(authorities);
+ } else {
+ log.debug("CustomRoleMapper: Decrosslist tool");
+ // decrosslist tool has a restriction of needing to be in a certain table to access the tool
+ String userId = oidcTokenUtils.getUserLoginId();
+
+ String rolesString = "NotAuthorized";
+
+ DecrosslistUser user = decrosslistUserRepository.findByUsername(userId);
+
+ if (user != null) {
+ rolesString = LTIConstants.CANVAS_INSTRUCTOR_ROLE;
+ }
+
+ String[] userRoles = rolesString.split(",");
+
+ String newAuthString = returnEquivalentAuthority(userRoles, getDefaultInstructorRoles());
+
+ OidcUserAuthority newUserAuth = new OidcUserAuthority(newAuthString, userAuth.getIdToken(), userAuth.getUserInfo());
+ remappedAuthorities.add(newUserAuth);
+ }
+ }
+
+ return remappedAuthorities;
+ }
}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/config/JpaRestConfig.java b/src/main/java/edu/iu/uits/lms/crosslist/config/JpaRestConfig.java
new file mode 100644
index 0000000..6bc084d
--- /dev/null
+++ b/src/main/java/edu/iu/uits/lms/crosslist/config/JpaRestConfig.java
@@ -0,0 +1,57 @@
+package edu.iu.uits.lms.crosslist.config;
+
+/*-
+ * #%L
+ * lms-lti-crosslist
+ * %%
+ * Copyright (C) 2015 - 2024 Indiana University
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Indiana University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import edu.iu.uits.lms.crosslist.model.DecrosslistUser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
+import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
+import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+
+@Configuration
+@Slf4j
+public class JpaRestConfig implements RepositoryRestConfigurer {
+
+ @Override
+ public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
+ // This is needed to allow the "ids" to be served up via the
+ // @RepositoryRestResource annotation (by default, it is suppressed)
+ config.exposeIdsFor(DecrosslistUser.class);
+
+ RepositoryRestConfigurer.super.configureRepositoryRestConfiguration(config, cors);
+ config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED);
+ }
+}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/config/PostgresDBConfig.java b/src/main/java/edu/iu/uits/lms/crosslist/config/PostgresDBConfig.java
index 5217961..c44c588 100644
--- a/src/main/java/edu/iu/uits/lms/crosslist/config/PostgresDBConfig.java
+++ b/src/main/java/edu/iu/uits/lms/crosslist/config/PostgresDBConfig.java
@@ -40,6 +40,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
@@ -51,6 +52,10 @@
import java.util.Map;
@Configuration("crosslistDbConfig")
+@EnableJpaRepositories(
+ entityManagerFactoryRef = "crosslistEntityMgrFactory",
+ transactionManagerRef = "crosslistTransactionMgr",
+ basePackages = {"edu.iu.uits.lms.crosslist.repository"})
@EnableTransactionManagement
public class PostgresDBConfig {
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java b/src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java
index cb29aff..723f48d 100644
--- a/src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java
+++ b/src/main/java/edu/iu/uits/lms/crosslist/config/SecurityConfig.java
@@ -36,6 +36,7 @@
import edu.iu.uits.lms.common.it12logging.LmsFilterSecurityInterceptorObjectPostProcessor;
import edu.iu.uits.lms.common.it12logging.RestSecurityLoggingConfig;
import edu.iu.uits.lms.common.oauth.CustomJwtAuthenticationConverter;
+import edu.iu.uits.lms.crosslist.repository.DecrosslistUserRepository;
import edu.iu.uits.lms.lti.repository.DefaultInstructorRoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
@@ -57,6 +58,30 @@
@EnableWebSecurity
public class SecurityConfig {
+ @Configuration
+ @Order(SecurityProperties.BASIC_AUTH_ORDER - 5)
+ public static class CrosslistRestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
+
+ @Override
+ public void configure(HttpSecurity http) throws Exception {
+ http
+ .cors().and()
+ .requestMatchers().antMatchers("/rest/**","/api/**")
+ .and()
+ .authorizeRequests()
+ .antMatchers("/api/**").permitAll()
+ .antMatchers("/rest/**")
+ .access("hasAuthority('SCOPE_lms:rest') and hasAuthority('ROLE_LMS_REST_ADMINS')")
+ .and()
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and()
+ .oauth2ResourceServer()
+ .jwt().jwtAuthenticationConverter(new CustomJwtAuthenticationConverter());
+
+ http.apply(new RestSecurityLoggingConfig());
+ }
+ }
+
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 4)
public static class CrosslistWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@@ -67,6 +92,9 @@ public static class CrosslistWebSecurityConfigurerAdapter extends WebSecurityCon
@Autowired
private ToolConfig toolConfig;
+ @Autowired
+ private DecrosslistUserRepository decrosslistUserRepository;
+
@Override
protected void configure(HttpSecurity http) throws Exception {
http
@@ -86,7 +114,7 @@ protected void configure(HttpSecurity http) throws Exception {
//Setup the LTI handshake
Lti13Configurer lti13Configurer = new Lti13Configurer()
- .grantedAuthoritiesMapper(new CustomRoleMapper(defaultInstructorRoleRepository, toolConfig));
+ .grantedAuthoritiesMapper(new CustomRoleMapper(defaultInstructorRoleRepository, toolConfig, decrosslistUserRepository));
http.apply(lti13Configurer);
@@ -112,29 +140,6 @@ public void configure(WebSecurity web) throws Exception {
}
- @Configuration
- @Order(SecurityProperties.BASIC_AUTH_ORDER - 3)
- public static class CrosslistRestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
-
- @Override
- public void configure(HttpSecurity http) throws Exception {
- http
- .cors().and()
- .requestMatchers().antMatchers("/rest/**")
- .and()
- .authorizeRequests()
- .antMatchers("/rest/**")
- .access("hasAuthority('SCOPE_lms:rest') and hasAuthority('ROLE_LMS_REST_ADMINS')")
- .and()
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- .oauth2ResourceServer()
- .jwt().jwtAuthenticationConverter(new CustomJwtAuthenticationConverter());
-
- http.apply(new RestSecurityLoggingConfig());
- }
- }
-
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 2)
public static class CrosslistCatchAllSecurityConfig extends WebSecurityConfigurerAdapter {
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/config/SwaggerConfig.java b/src/main/java/edu/iu/uits/lms/crosslist/config/SwaggerConfig.java
new file mode 100644
index 0000000..9acfefd
--- /dev/null
+++ b/src/main/java/edu/iu/uits/lms/crosslist/config/SwaggerConfig.java
@@ -0,0 +1,66 @@
+package edu.iu.uits.lms.crosslist.config;
+
+/*-
+ * #%L
+ * lms-lti-crosslist
+ * %%
+ * Copyright (C) 2015 - 2024 Indiana University
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Indiana University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.security.OAuthFlow;
+import io.swagger.v3.oas.annotations.security.OAuthFlows;
+import io.swagger.v3.oas.annotations.security.OAuthScope;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityScheme;
+import org.springdoc.core.GroupedOpenApi;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+@Profile("swagger")
+@Configuration
+@OpenAPIDefinition(info = @Info(title = "Crosslist REST Endpoints", version = "${lms-lti-crosslist.version}"),
+ security = @SecurityRequirement(name = "security_auth_crosslist"))
+@SecurityScheme(name = "security_auth_crosslist", type = SecuritySchemeType.OAUTH2,
+ flows = @OAuthFlows(authorizationCode = @OAuthFlow(
+ authorizationUrl = "${springdoc.oAuthFlow.authorizationUrl}",
+ scopes = {@OAuthScope(name = "lms:rest")},
+ tokenUrl = "${springdoc.oAuthFlow.tokenUrl}")))
+public class SwaggerConfig {
+ @Bean
+ public GroupedOpenApi groupedOpenApi() {
+ return GroupedOpenApi.builder()
+ .group("crosslist")
+ .packagesToScan("edu.iu.uits.lms.crosslist.controller.rest")
+ .build();
+ }
+}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/controller/CrosslistController.java b/src/main/java/edu/iu/uits/lms/crosslist/controller/CrosslistController.java
index b1eeabc..90fdc76 100644
--- a/src/main/java/edu/iu/uits/lms/crosslist/controller/CrosslistController.java
+++ b/src/main/java/edu/iu/uits/lms/crosslist/controller/CrosslistController.java
@@ -886,78 +886,6 @@ public String endSelfImpersonation(@PathVariable("courseId") String courseId, @M
return main(courseId, model, session);
}
- @RequestMapping("/lookup-launch")
- @Secured({LTIConstants.ADMIN_AUTHORITY})
- public String lookupLaunch(@ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
- getTokenWithoutContext();
-
- Date nowDate = new Date();
-
- List terms = termService.getEnrollmentTerms()
- .stream()
- .filter(term -> term.getSisTermId().compareTo("4218") >= 0 && term.getSisTermId().charAt(0) == '4')
- .filter(term -> CanvasDateFormatUtil.string2DateOnly(term.getStartAt()).compareTo(nowDate) < 0)
- .sorted(Comparator.comparing(CanvasTerm::getSisTermId).reversed())
- .toList();
-
- if (courseSessionService.getAttributeFromSession(session, "all", "terms", List.class) == null) {
- courseSessionService.addAttributeToSession(session, "all", "terms", terms);
- }
-
- model.addAttribute("terms", terms);
-
- return "findParentCourse";
- }
-
- @PostMapping(value = "/lookup-search-sisid")
- @Secured({LTIConstants.ADMIN_AUTHORITY})
- public String lookupSearchBySisId(@ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
- getTokenWithoutContext();
-
- FindParentResult findParentResult = null;
-
- List terms = courseSessionService.getAttributeFromSession(session, "all",
- "terms", List.class);
-
- SisCourse sisCourse = sisService.getLegacySisCourseBySiteId(findParentModel.getSisIdSearch().trim().toUpperCase());
- findParentResult = crosslistService.processSisLookup(sisCourse);
-
- model.addAttribute("terms", terms);
-
- if (findParentResult != null) {
- model.addAttribute("findParentResult", findParentResult);
- }
-
- return "findParentCourse";
- }
-
- @PostMapping(value = "/lookup-search-termandclassnumber")
- @Secured({LTIConstants.ADMIN_AUTHORITY})
- public String lookupSearchByTermAndClassNUmber(@ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
- getTokenWithoutContext();
-
- FindParentResult findParentResult = null;
-
- List terms = courseSessionService.getAttributeFromSession(session, "all",
- "terms", List.class);
-
- final String strm = findParentModel.getTermByClassNumberSearch().trim();
- final String classNumber = findParentModel.getClassNumberSearch().trim();
-
- final String iuSiteId = sisService.getIuSiteIdFromStrmAndClassNumber(strm, classNumber);
-
- SisCourse sisCourse = sisService.getSisCourseBySiteId(iuSiteId);
- findParentResult = crosslistService.processSisLookup(sisCourse);
-
- model.addAttribute("terms", terms);
-
- if (findParentResult != null) {
- model.addAttribute("findParentResult", findParentResult);
- }
-
- return "findParentCourse";
- }
-
private List removeSectionUiDisplayBySectionName(@NonNull List oldList, @NonNull String toRemoveSectionName) {
List newList = new ArrayList();
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/controller/DecrosslistController.java b/src/main/java/edu/iu/uits/lms/crosslist/controller/DecrosslistController.java
new file mode 100644
index 0000000..64698d8
--- /dev/null
+++ b/src/main/java/edu/iu/uits/lms/crosslist/controller/DecrosslistController.java
@@ -0,0 +1,188 @@
+package edu.iu.uits.lms.crosslist.controller;
+
+/*-
+ * #%L
+ * lms-lti-crosslist
+ * %%
+ * Copyright (C) 2015 - 2024 Indiana University
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Indiana University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import edu.iu.uits.lms.canvas.model.Section;
+import edu.iu.uits.lms.canvas.services.SectionService;
+import edu.iu.uits.lms.crosslist.CrosslistConstants;
+import edu.iu.uits.lms.crosslist.model.DecrosslistAudit;
+import edu.iu.uits.lms.crosslist.model.DecrosslistUser;
+import edu.iu.uits.lms.crosslist.model.FindParentModel;
+import edu.iu.uits.lms.crosslist.model.FindParentResult;
+import edu.iu.uits.lms.crosslist.model.SubmissionStatus;
+import edu.iu.uits.lms.crosslist.repository.DecrosslistAuditRepository;
+import edu.iu.uits.lms.crosslist.repository.DecrosslistUserRepository;
+import edu.iu.uits.lms.crosslist.service.CrosslistService;
+import edu.iu.uits.lms.lti.LTIConstants;
+import edu.iu.uits.lms.lti.controller.OidcTokenAwareController;
+import edu.iu.uits.lms.lti.service.OidcTokenUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.support.ResourceBundleMessageSource;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import uk.ac.ox.ctl.lti13.security.oauth2.client.lti.authentication.OidcAuthenticationToken;
+
+import javax.servlet.http.HttpSession;
+import java.util.List;
+import java.util.Locale;
+
+@Controller
+@Slf4j
+@RequestMapping("/app")
+public class DecrosslistController extends OidcTokenAwareController {
+
+ @Autowired
+ private CrosslistService crosslistService;
+
+ @Autowired
+ private SectionService sectionService;
+
+ @Autowired
+ private ResourceBundleMessageSource messageSource;
+
+ @Autowired
+ private DecrosslistUserRepository decrosslistUserRepository;
+
+ @Autowired
+ private DecrosslistAuditRepository decrosslistAuditRepository;
+
+ @RequestMapping("/lookup-launch")
+ @Secured({LTIConstants.INSTRUCTOR_AUTHORITY})
+ public String lookupLaunch(@ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
+ getTokenWithoutContext();
+
+ return "findParentCourse";
+ }
+
+ @PostMapping(value = "/lookup-search-sisid")
+ @Secured({LTIConstants.INSTRUCTOR_AUTHORITY})
+ public String lookupSearchBySisId(@ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
+ getTokenWithoutContext();
+
+ FindParentResult findParentResult = null;
+
+ // pass off all the lookup knowledge and data we need to the service
+ String sisIdSearch = findParentModel.getSisIdSearch().trim();
+ findParentResult = crosslistService.processSisLookup(sisIdSearch);
+
+ if (findParentResult != null) {
+ model.addAttribute("findParentResult", findParentResult);
+ // add canvasCourseId to be used in audit log purposes later
+ model.addAttribute("canvasCourseId", findParentResult.getCanvasCourseId());
+ }
+
+ return "findParentCourse";
+ }
+
+ @PostMapping(value = "/decrosslist-sections")
+ @Secured({LTIConstants.INSTRUCTOR_AUTHORITY})
+ public String decrossListSections(@RequestParam("section-checkboxes") List sectionIds,
+ @RequestParam("canvasCourseId") String canvasCourseId,
+ @ModelAttribute FindParentModel findParentModel, Model model, HttpSession session) {
+ OidcAuthenticationToken token = getTokenWithoutContext();
+ OidcTokenUtils oidcTokenUtils = new OidcTokenUtils(token);
+ // get current username for logging purposes
+ String username = oidcTokenUtils.getUserLoginId();
+ // get official user info from database table for the audit log
+ DecrosslistUser userInfo = decrosslistUserRepository.findByUsername(username);
+
+ boolean hasSuccesses = false;
+ boolean hasErrors = false;
+
+ // loop through the list of section ids and decrosslist them!
+ for (String sectionId : sectionIds) {
+ log.debug("Decrosslisting section: " + sectionId);
+ Section section = sectionService.decrossList(sectionId);
+ if (section != null) {
+ log.debug("Decrosslisted Section: " + section);
+ hasSuccesses = true;
+ DecrosslistAudit audit = new DecrosslistAudit();
+ audit.setDecrosslistedFrom(canvasCourseId);
+ audit.setSisSection(sectionId);
+ audit.setUsername(userInfo.getUsername());
+ audit.setCanvasUserId(userInfo.getCanvasUserId());
+ audit.setDisplayName(userInfo.getDisplayName());
+ decrosslistAuditRepository.save(audit);
+ } else {
+ hasErrors = true;
+ }
+ }
+
+ if (hasErrors || hasSuccesses) {
+ SubmissionStatus status = new SubmissionStatus();
+ String titleKey = null;
+ String messageKey = null;
+
+ if (hasSuccesses && !hasErrors) {
+ status.setStatusClass(CrosslistConstants.STATUS_SUCCESS);
+ titleKey = "decross.status.success.title";
+ messageKey = "decross.status.success.msg";
+ } else if (hasSuccesses) {
+ status.setStatusClass(CrosslistConstants.STATUS_PARTIAL);
+ titleKey = "decross.status.partial.title";
+ messageKey = "decross.status.partial.msg";
+ } else {
+ status.setStatusClass(CrosslistConstants.STATUS_FAILED);
+ titleKey = "decross.status.error.title";
+ messageKey = "decross.status.error.msg";
+ }
+
+ String statusMessage = messageSource.getMessage(messageKey, null, Locale.getDefault());
+ String statusTitle = messageSource.getMessage(titleKey, null, Locale.getDefault());
+ status.setStatusMessage(statusMessage);
+ status.setStatusTitle(statusTitle);
+ model.addAttribute("submissionStatus", status);
+ }
+
+ FindParentResult findParentResult = null;
+
+ // pass off all the lookup knowledge and data we need to the service
+ String sisIdSearch = findParentModel.getSisIdSearch().trim();
+ findParentResult = crosslistService.processSisLookup(sisIdSearch);
+
+ if (findParentResult != null) {
+ model.addAttribute("findParentResult", findParentResult);
+ // add canvasCourseId to be used in audit log purposes later
+ model.addAttribute("canvasCourseId", findParentResult.getCanvasCourseId());
+ }
+
+ return "findParentCourse";
+ }
+}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/model/DecrosslistAudit.java b/src/main/java/edu/iu/uits/lms/crosslist/model/DecrosslistAudit.java
new file mode 100644
index 0000000..b46655d
--- /dev/null
+++ b/src/main/java/edu/iu/uits/lms/crosslist/model/DecrosslistAudit.java
@@ -0,0 +1,86 @@
+package edu.iu.uits.lms.crosslist.model;
+
+/*-
+ * #%L
+ * lms-lti-crosslist
+ * %%
+ * Copyright (C) 2015 - 2024 Indiana University
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Indiana University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import edu.iu.uits.lms.common.date.DateFormatUtil;
+import lombok.Data;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.PrePersist;
+import javax.persistence.PreUpdate;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import java.util.Date;
+
+@Entity
+@Table(name = "DECROSSLIST_AUDIT")
+@SequenceGenerator(name = "DECROSSLIST_AUDIT_ID_SEQ", sequenceName = "DECROSSLIST_AUDIT_ID_SEQ", allocationSize = 1)
+@Data
+public class DecrosslistAudit {
+ @Id
+ @GeneratedValue(generator = "DECROSSLIST_AUDIT_ID_SEQ")
+ @Column(name = "DECROSSLIST_AUDIT_ID")
+ private Long id;
+
+ @JsonFormat(pattern = DateFormatUtil.JSON_DATE_FORMAT)
+ @Column(name = "AUDIT_DATE")
+ private Date auditDate;
+
+ @Column(name = "DECROSSLISTED_FROM")
+ private String decrosslistedFrom;
+
+ @Column(name = "SIS_SECTION")
+ private String sisSection;
+
+ @Column(name = "USERNAME")
+ private String username;
+
+ @Column(name = "CANVAS_USER_ID")
+ private String canvasUserId;
+
+ @Column(name = "DISPLAY_NAME")
+ private String displayName;
+
+ @PreUpdate
+ @PrePersist
+ public void updateTimeStamps() {
+ if (auditDate==null) {
+ auditDate = new Date();
+ }
+ }
+}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/model/DecrosslistUser.java b/src/main/java/edu/iu/uits/lms/crosslist/model/DecrosslistUser.java
new file mode 100644
index 0000000..185dda3
--- /dev/null
+++ b/src/main/java/edu/iu/uits/lms/crosslist/model/DecrosslistUser.java
@@ -0,0 +1,88 @@
+package edu.iu.uits.lms.crosslist.model;
+
+/*-
+ * #%L
+ * lms-lti-crosslist
+ * %%
+ * Copyright (C) 2015 - 2024 Indiana University
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Indiana University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import edu.iu.uits.lms.common.date.DateFormatUtil;
+import lombok.Data;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.PrePersist;
+import javax.persistence.PreUpdate;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.io.Serializable;
+import java.util.Date;
+
+@Entity
+@Table(name = "DECROSSLIST_USERS", uniqueConstraints = @UniqueConstraint(name = "UK_DECROSSLIST_USERS", columnNames = {"username"}))
+@SequenceGenerator(name = "DECROSSLIST_USERS_ID_SEQ", sequenceName = "DECROSSLIST_USERS_ID_SEQ", allocationSize = 1)
+@Data
+public class DecrosslistUser implements Serializable {
+ @Id
+ @GeneratedValue(generator = "DECROSSLIST_USERS_ID_SEQ")
+ @Column(name = "DECROSSLIST_USER_ID")
+ private Long id;
+
+ @Column(name = "USERNAME")
+ private String username;
+
+ @Column(name = "CANVAS_USER_ID")
+ private String canvasUserId;
+
+ @Column(name = "DISPLAY_NAME")
+ private String displayName;
+
+ @JsonFormat(pattern = DateFormatUtil.JSON_DATE_FORMAT)
+ @Column(name = "CREATEDON")
+ private Date createdOn;
+
+ @JsonFormat(pattern = DateFormatUtil.JSON_DATE_FORMAT)
+ @Column(name = "MODIFIEDON")
+ private Date modifiedOn;
+
+
+ @PreUpdate
+ @PrePersist
+ public void updateTimeStamps() {
+ modifiedOn = new Date();
+ if (createdOn==null) {
+ createdOn = new Date();
+ }
+ }
+}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentModel.java b/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentModel.java
index 21201be..57e12a1 100644
--- a/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentModel.java
+++ b/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentModel.java
@@ -37,8 +37,5 @@
@Data
public class FindParentModel {
- private String radioSearch;
private String sisIdSearch;
- private String termByClassNumberSearch;
- private String classNumberSearch;
}
\ No newline at end of file
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentResult.java b/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentResult.java
index ca1a627..894ba6a 100644
--- a/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentResult.java
+++ b/src/main/java/edu/iu/uits/lms/crosslist/model/FindParentResult.java
@@ -33,9 +33,11 @@
* #L%
*/
-import edu.iu.uits.lms.crosslist.CrosslistConstants;
+import edu.iu.uits.lms.canvas.model.Section;
import lombok.Data;
+import java.util.List;
+
@Data
public class FindParentResult {
private String url;
@@ -45,4 +47,6 @@ public class FindParentResult {
private String statusMessage;
private String statusIconCssClasses;
private String statusIconName;
+ private String canvasCourseId;
+ private List sectionList;
}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/repository/DecrosslistAuditRepository.java b/src/main/java/edu/iu/uits/lms/crosslist/repository/DecrosslistAuditRepository.java
new file mode 100644
index 0000000..6c4d0dd
--- /dev/null
+++ b/src/main/java/edu/iu/uits/lms/crosslist/repository/DecrosslistAuditRepository.java
@@ -0,0 +1,43 @@
+package edu.iu.uits.lms.crosslist.repository;
+
+/*-
+ * #%L
+ * lms-lti-crosslist
+ * %%
+ * Copyright (C) 2015 - 2024 Indiana University
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Indiana University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import edu.iu.uits.lms.crosslist.model.DecrosslistAudit;
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+public interface DecrosslistAuditRepository extends PagingAndSortingRepository {
+
+}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/repository/DecrosslistUserRepository.java b/src/main/java/edu/iu/uits/lms/crosslist/repository/DecrosslistUserRepository.java
new file mode 100644
index 0000000..4b0bc89
--- /dev/null
+++ b/src/main/java/edu/iu/uits/lms/crosslist/repository/DecrosslistUserRepository.java
@@ -0,0 +1,53 @@
+package edu.iu.uits.lms.crosslist.repository;
+
+/*-
+ * #%L
+ * lms-lti-crosslist
+ * %%
+ * Copyright (C) 2015 - 2024 Indiana University
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Indiana University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import edu.iu.uits.lms.crosslist.model.DecrosslistUser;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.data.repository.query.Param;
+import org.springframework.data.rest.core.annotation.Description;
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.CrossOrigin;
+
+@Component
+@RepositoryRestResource(path = "decrosslist_users",
+ itemResourceDescription = @Description("decrosslist_users item resource description"),
+ collectionResourceDescription = @Description("decrosslist_users collection resource description"))
+@Tag(name = "DecrosslistUserRepository", description = "Operations involving the DecrosslistUser table")
+@CrossOrigin(origins = {"${lms.swagger.cors.origin}"})
+public interface DecrosslistUserRepository extends PagingAndSortingRepository {
+ DecrosslistUser findByUsername(@Param("username") String username);
+}
diff --git a/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java b/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java
index 9e52751..5ae842e 100644
--- a/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java
+++ b/src/main/java/edu/iu/uits/lms/crosslist/service/CrosslistService.java
@@ -389,10 +389,11 @@ public boolean canCoursesBeCrosslistedBasedOnEtexts(String sourceSisCourseSiteId
return sourceCourseEtextIsbns.equals(destinationCourseEtextIsbns);
}
- public FindParentResult processSisLookup(SisCourse sisCourse) {
+ // this method is only used in the decrosslist tool
+ public FindParentResult processSisLookup(String sisSectionId) {
FindParentResult findParentResult = new FindParentResult();
- if (sisCourse == null || sisCourse.getIuSiteId() == null) {
+ if (sisSectionId == null || sisSectionId.isEmpty()) {
findParentResult.setShowCourseInfo(false);
findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_SIS_MESSAGE);
findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS);
@@ -400,9 +401,9 @@ public FindParentResult processSisLookup(SisCourse sisCourse) {
return findParentResult;
}
- Section section = sectionService.getSection(String.format("sis_section_id:%s", sisCourse.getIuSiteId()));
+ Section section = sectionService.getSection(String.format("sis_section_id:%s", sisSectionId));
- if (section == null || section.getSis_course_id() == null || section.getSis_section_id() == null) {
+ if (section == null) {
findParentResult.setShowCourseInfo(false);
findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE);
findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS);
@@ -420,15 +421,16 @@ public FindParentResult processSisLookup(SisCourse sisCourse) {
return findParentResult;
}
- if (section.getSis_course_id().equals(section.getSis_section_id())) {
- findParentResult.setShowCourseInfo(true);
- findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_COURSE_NOT_CROSSLISTED_MESSAGE);
+ // separate sections call since sections are null in a course lookup to the Canvas API
+ // fine with this being empty, even though that is unlikely
+ // null is not ok
+ List sectionsList = courseService.getCourseSections(section.getCourse_id());
+
+ if (sectionsList == null) {
+ findParentResult.setShowCourseInfo(false);
+ findParentResult.setStatusMessage(CrosslistConstants.LOOKUP_FAILURE_NOT_FOUND_IN_CANVAS_MESSAGE);
findParentResult.setStatusIconCssClasses(CrosslistConstants.LOOKUP_FAILURE_CSS);
findParentResult.setStatusIconName(CrosslistConstants.LOOKUP_FAILURE_ICON_NAME);
- findParentResult.setName(course.getName());
- findParentResult.setSisCourseId(course.getSisCourseId());
- findParentResult.setUrl(String.format("%s/courses/%s",
- canvasConfiguration.getBaseUrl(), course.getId()));
return findParentResult;
}
@@ -439,7 +441,9 @@ public FindParentResult processSisLookup(SisCourse sisCourse) {
findParentResult.setName(course.getName());
findParentResult.setSisCourseId(course.getSisCourseId());
findParentResult.setUrl(String.format("%s/courses/%s",
- canvasConfiguration.getBaseUrl(), course.getId()));
+ canvasConfiguration.getBaseUrl(), section.getCourse_id()));
+ findParentResult.setCanvasCourseId(course.getId());
+ findParentResult.setSectionList(sectionsList);
return findParentResult;
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index ab0febd..150416c 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -4,6 +4,9 @@ spring:
session:
store-type: none
config.import: optional:${app.fullFilePath}/${app.overridesFileName}, optional:${app.fullFilePath}/security.properties, classpath:canvas.properties, optional:classpath:git.properties, optional:classpath:denodo.properties, classpath:lti-registrations.properties
+ data:
+ rest:
+ base-path: /rest
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
hibernate:
@@ -88,9 +91,18 @@ spring:
spring:
config:
activate.on-profile: swagger
- import: classpath:lti-api-config.properties
springdoc:
+ api-docs:
+ enabled: true
+ path: /api/v3/api-docs
+ cache.disabled: true
+ packagesToScan: edu.iu.uits.lms.crosslist.controller.rest
swagger-ui:
+ enabled: false
+ disable-swagger-default-url: true
+ path: /api/swagger-ui.html
+ # Setting supportedSubmitMethods to an empty list is what turns off the "try it out" button
+ # supportedSubmitMethods:
oauth:
clientId: ${oauth.tokenprovider.clientId}
oAuthFlow:
diff --git a/src/main/resources/crosslist.properties b/src/main/resources/crosslist.properties
index e0712d2..72f6fd6 100644
--- a/src/main/resources/crosslist.properties
+++ b/src/main/resources/crosslist.properties
@@ -37,3 +37,10 @@ status.partial.msg=Some of your cross-listing changes were not successful. See b
status.success.title=Success
status.success.msg=Your cross-listing changes were successful!
etext.message=An IU eTexts order is associated with the original section in this course. Before taking any action, make sure the same materials were ordered for all the sections you wish to cross-list.
+
+decross.status.error.title=Error
+decross.status.error.msg=Your de-cross-listing changes failed. See below for the current list of sections in this course, and try your changes again.
+decross.status.partial.title=Alert
+decross.status.partial.msg=Some of your de-cross-listing changes were not successful. See below for the current list of sections in this course. To make additional changes, try again.
+decross.status.success.title=Success
+decross.status.success.msg=Your de-cross-listing changes were successful!
diff --git a/src/main/resources/static/css/crosslisting.css b/src/main/resources/static/css/crosslisting.css
index 5d04cf5..09cc97d 100644
--- a/src/main/resources/static/css/crosslisting.css
+++ b/src/main/resources/static/css/crosslisting.css
@@ -92,10 +92,14 @@
filter: brightness(0%);
}
-.resultsWidth {
- width: max-content;
-}
-
.crosslisted-icon {
vertical-align: middle;
+}
+
+/* decrosslisting section */
+
+/* force first column to be small and override line-height to vertically center checkbox on header */
+.checkboxColumn {
+ width: 1%;
+ line-height: inherit;
}
\ No newline at end of file
diff --git a/src/main/resources/templates/decrosslistlayout.html b/src/main/resources/templates/decrosslistlayout.html
new file mode 100644
index 0000000..66245a5
--- /dev/null
+++ b/src/main/resources/templates/decrosslistlayout.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+ De-cross-listing sections
+
+
+
+
+
+
+
+
+