diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..30e3b27b --- /dev/null +++ b/build.gradle @@ -0,0 +1,88 @@ +buildscript { + ext.kotlin_version = '1.2.41' + + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.5.RELEASE") + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin + + } +} + +apply plugin: 'java' +apply plugin: "kotlin" +apply plugin: "kotlin-spring" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin +apply plugin: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' + +repositories { + mavenCentral() + mavenLocal() +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +dependencies { + compile "org.springframework.boot:spring-boot-starter-web" + compile "org.springframework.boot:spring-boot-starter-validation" + compile "org.springframework.boot:spring-boot-starter-freemarker" + + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + + compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.7" + compile "com.fasterxml.jackson.core:jackson-core:2.9.7" + compile "com.fasterxml.jackson.core:jackson-databind:2.9.7" + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + + compile "commons-fileupload:commons-fileupload:1.3.1" + compile "uk.co.squadlist:squadlist-api-client:0.0.1-SNAPSHOT" + compile "com.google.guava:guava:16.0" + compile "org.apache.commons:commons-csv:1.0" + compile "org.mnode.ical4j:ical4j:1.0.2" + compile "oro:oro:2.0.8" + compile "org.slf4j:slf4j-api:1.6.1" + compile "org.codehaus.jackson:jackson-core-asl:1.9.4" + compile "org.codehaus.jackson:jackson-mapper-asl:1.9.4" + compile "joda-time:joda-time:2.1" + compile "uk.co.eelpieconsulting.common:common-dates:1.0.10" + compile "jpwgen:jpwgen:1.2.0" + compile "commons-cli:commons-cli:1.2" + compile "com.restfb:restfb:1.46.0" + compile "commons-io:commons-io:2.4" + compile "org.apache.velocity:velocity:1.7" + + compile "org.springframework:spring-aop" + compile "org.aspectj:aspectjweaver:1.7.0" + + compile ('log4j:log4j:1.2.17') + + testCompile("org.springframework.boot:spring-boot-starter-test") + testCompile("junit:junit:4.12") + testRuntime("org.junit.vintage:junit-vintage-engine:5.3.1") + testCompile 'org.mockito:mockito-core:1.10.8' + + compile "org.springframework.boot:spring-boot-devtools" + +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} + +test { + useJUnitPlatform() +} diff --git a/build.xml b/build.xml deleted file mode 100644 index ea67090f..00000000 --- a/build.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/repository/jpwgen/jpwgen/1.2.0/jpwgen-1.2.0.jar b/lib/repository/jpwgen/jpwgen/1.2.0/jpwgen-1.2.0.jar deleted file mode 100644 index 08dfd9b1..00000000 Binary files a/lib/repository/jpwgen/jpwgen/1.2.0/jpwgen-1.2.0.jar and /dev/null differ diff --git a/pom.xml b/pom.xml deleted file mode 100644 index f0e277d0..00000000 --- a/pom.xml +++ /dev/null @@ -1,229 +0,0 @@ - - 4.0.0 - uk.co.squadlist - squadlist - jar - squadlist - 0.0.1-SNAPSHOT - squadlist - - - org.springframework.boot - spring-boot-starter-parent - 1.2.8.RELEASE - - - - - local-dependencies - file:${basedir}/lib/repository - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - 2.0.2 - - - org.apache.maven.plugins - maven-surefire-plugin - 2.17 - - - org.springframework.boot - spring-boot-maven-plugin - - - - - - - commons-fileupload - commons-fileupload - 1.3.1 - - - - uk.co.squadlist - squadlist-api-client - 0.0.1-SNAPSHOT - - - - com.google.guava - guava - 16.0 - - - - org.apache.commons - commons-csv - 1.0 - - - - org.mnode.ical4j - ical4j - 1.0.2 - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework - spring-context-support - - - - org.apache.velocity - velocity - - - oro - oro - 2.0.8 - - - - org.slf4j - slf4j-api - 1.6.1 - - - - org.hibernate - hibernate-validator-annotation-processor - 4.1.0.Final - - - - org.codehaus.jackson - jackson-core-asl - 1.9.4 - - - org.codehaus.jackson - jackson-mapper-asl - 1.9.4 - - - - joda-time - joda-time - 2.1 - - - - javax.servlet - servlet-api - 2.5 - jar - provided - - - - log4j - log4j - 1.2.16 - - - - org.kohsuke - github-api - 1.59 - - - - uk.co.eelpieconsulting.common - common-dates - 1.0.10 - - - uk.co.eelpieconsulting.common - common-views - 1.0.11 - - - - - jpwgen - jpwgen - 1.2.0 - - - - commons-cli - commons-cli - 1.2 - - - - com.restfb - restfb - 1.46.0 - - - - commons-io - commons-io - 2.4 - - - - - org.springframework - spring-aop - - - org.aspectj - aspectjweaver - 1.7.0 - - - cglib - cglib - 2.2.2 - - - - junit - junit - 4.8.2 - test - - - org.mockito - mockito-core - 1.10.8 - test - - - org.springframework - spring-mock - 2.0.8 - test - - - - org.glassfish.jaxb - jaxb-runtime - 2.3.0 - - - javax.activation - javax.activation-api - 1.2.0 - - - - diff --git a/squadlist.yaml b/squadlist.yaml deleted file mode 100644 index 449f06a0..00000000 --- a/squadlist.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: squadlist -spec: - type: NodePort - selector: - app: squadlist - ports: - - port: 8080 - nodePort: 32200 - name: squadlist ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: squadlist -spec: - replicas: 1 - template: - metadata: - labels: - app: squadlist - spec: - containers: - - name: squadlist - image: localhost:5000/squadlist:1.0 - ports: - - containerPort: 8080 - volumeMounts: - - name: config-volume - mountPath: /opt/squadlist/conf - volumes: - - name: config-volume - configMap: - name: squadlist-config diff --git a/src/main/java/uk/co/squadlist/web/Main.java b/src/main/java/uk/co/squadlist/web/Main.java index 1318a9f3..d6af0f24 100644 --- a/src/main/java/uk/co/squadlist/web/Main.java +++ b/src/main/java/uk/co/squadlist/web/Main.java @@ -1,124 +1,105 @@ package uk.co.squadlist.web; -import com.google.common.collect.Maps; import org.apache.log4j.Logger; -import org.apache.velocity.app.Velocity; -import org.apache.velocity.runtime.RuntimeConstants; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.embedded.FilterRegistrationBean; -import org.springframework.boot.context.embedded.MultipartConfigFactory; import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.web.context.request.RequestContextListener; -import org.springframework.web.filter.CharacterEncodingFilter; -import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.view.velocity.VelocityConfigurer; -import org.springframework.web.servlet.view.velocity.VelocityViewResolver; -import uk.co.eelpieconsulting.common.views.EtagGenerator; -import uk.co.eelpieconsulting.common.views.ViewFactory; -import uk.co.squadlist.web.auth.LoggedInUserService; -import uk.co.squadlist.web.urls.UrlBuilder; -import uk.co.squadlist.web.views.*; -import javax.servlet.MultipartConfigElement; -import java.io.IOException; -import java.util.Map; - -@ComponentScan(basePackages="uk.co.squadlist.web") +@ComponentScan(basePackages = "uk.co.squadlist.web") @Configuration @EnableWebMvc @EnableScheduling @EnableAutoConfiguration public class Main { - - private final static Logger log = Logger.getLogger(Main.class); - - private static ApplicationContext ctx; - public static void main(String[] args) throws Exception { - ctx = SpringApplication.run(Main.class, args); - } + private final static Logger log = Logger.getLogger(Main.class); + + private static ApplicationContext ctx; + + public static void main(String[] args) throws Exception { + ctx = SpringApplication.run(Main.class, args); + } + + /* + @Bean + public CommonsMultipartResolver multipartResolver(@Value("${maximum.upload}") Long maxUploadSize) throws IOException { + final CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); + multipartResolver.setMaxUploadSize(maxUploadSize); + return multipartResolver; + } + + @Bean + MultipartConfigElement multipartConfigElement() { + MultipartConfigFactory factory = new MultipartConfigFactory(); + factory.setMaxFileSize("10MB"); + factory.setMaxRequestSize("10MB"); + return factory.createMultipartConfig(); + } +*/ - @Bean - public CommonsMultipartResolver multipartResolver(@Value("${maximum.upload}") Long maxUploadSize) throws IOException { - final CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); - multipartResolver.setMaxUploadSize(maxUploadSize); - return multipartResolver; - } - - @Bean - MultipartConfigElement multipartConfigElement() { - MultipartConfigFactory factory = new MultipartConfigFactory(); - factory.setMaxFileSize("10MB"); - factory.setMaxRequestSize("10MB"); - return factory.createMultipartConfig(); - } - - @Bean - public ViewFactory viewFactory() throws IOException { - return new ViewFactory(new EtagGenerator()); - } + /* + @Bean + public FilterRegistrationBean filterRegistrationBean() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); + registrationBean.setFilter(characterEncodingFilter); + characterEncodingFilter.setEncoding("UTF-8"); + characterEncodingFilter.setForceEncoding(true); + registrationBean.setOrder(Integer.MIN_VALUE); + registrationBean.addUrlPatterns("/*"); + return registrationBean; + } + */ - @Bean - public FilterRegistrationBean filterRegistrationBean() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean(); - CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); - registrationBean.setFilter(characterEncodingFilter); - characterEncodingFilter.setEncoding("UTF-8"); - characterEncodingFilter.setForceEncoding(true); - registrationBean.setOrder(Integer.MIN_VALUE); - registrationBean.addUrlPatterns("/*"); - return registrationBean; - } + /* + @Bean + public VelocityConfigurer velocityConfigurer() { + final VelocityConfigurer vc = new VelocityConfigurer(); + final Map velocityPropertiesMap = Maps.newHashMap(); + velocityPropertiesMap.put(Velocity.OUTPUT_ENCODING, "UTF-8"); + velocityPropertiesMap.put(Velocity.INPUT_ENCODING, "UTF-8"); + velocityPropertiesMap.put(RuntimeConstants.RESOURCE_LOADER, "classpath"); + velocityPropertiesMap.put("eventhandler.referenceinsertion.class", "org.apache.velocity.app.event.implement.EscapeHtmlReference"); + vc.setVelocityPropertiesMap(velocityPropertiesMap); + return vc; + } - @Bean - public VelocityConfigurer velocityConfigurer() { - final VelocityConfigurer vc = new VelocityConfigurer(); - final Map velocityPropertiesMap = Maps.newHashMap(); - velocityPropertiesMap.put(Velocity.OUTPUT_ENCODING, "UTF-8"); - velocityPropertiesMap.put(Velocity.INPUT_ENCODING, "UTF-8"); - velocityPropertiesMap.put(RuntimeConstants.RESOURCE_LOADER, "classpath"); - velocityPropertiesMap.put("eventhandler.referenceinsertion.class", "org.apache.velocity.app.event.implement.EscapeHtmlReference"); - vc.setVelocityPropertiesMap(velocityPropertiesMap); - return vc; - } + @Bean + public VelocityViewResolver velocityViewResolver(CssHelper cssHelper, DateFormatter dateFormatter, + DateHelper dateHelper, LoggedInUserService loggedInUserService, + PermissionsHelper permissionsHelper, SquadNamesHelper squadNamesHelper, + TextHelper textHelper, + UrlBuilder urlBuilder) { + final VelocityViewResolver viewResolver = new VelocityViewResolver(); + viewResolver.setCache(true); + viewResolver.setPrefix(""); + viewResolver.setSuffix(".vm"); + viewResolver.setContentType("text/html;charset=UTF-8"); - @Bean - public VelocityViewResolver velocityViewResolver(CssHelper cssHelper, DateFormatter dateFormatter, - DateHelper dateHelper, LoggedInUserService loggedInUserService, - PermissionsHelper permissionsHelper, SquadNamesHelper squadNamesHelper, - TextHelper textHelper, - UrlBuilder urlBuilder) { - final VelocityViewResolver viewResolver = new VelocityViewResolver(); - viewResolver.setCache(true); - viewResolver.setPrefix(""); - viewResolver.setSuffix(".vm"); - viewResolver.setContentType("text/html;charset=UTF-8"); - - final Map attributes = Maps.newHashMap(); - attributes.put("cssHelper", cssHelper); - attributes.put("dateFormatter", dateFormatter); - attributes.put("dateHelper", dateHelper); - attributes.put("loggedInUserService", loggedInUserService); - attributes.put("permissionsHelper", permissionsHelper); - attributes.put("squadNamesHelper", squadNamesHelper); - attributes.put("text", textHelper); - attributes.put("urlBuilder", urlBuilder); - viewResolver.setAttributesMap(attributes); - return viewResolver; - } + final Map attributes = Maps.newHashMap(); + attributes.put("cssHelper", cssHelper); + attributes.put("dateFormatter", dateFormatter); + attributes.put("dateHelper", dateHelper); + attributes.put("loggedInUserService", loggedInUserService); + attributes.put("permissionsHelper", permissionsHelper); + attributes.put("squadNamesHelper", squadNamesHelper); + attributes.put("text", textHelper); + attributes.put("urlBuilder", urlBuilder); + viewResolver.setAttributesMap(attributes); + return viewResolver; + } + */ - @Bean - @ConditionalOnMissingBean(RequestContextListener.class) - public RequestContextListener requestContextListener() { - return new RequestContextListener(); - } + /* + @Bean + @ConditionalOnMissingBean(RequestContextListener.class) + public RequestContextListener requestContextListener() { + return new RequestContextListener(); + } + */ } diff --git a/src/main/java/uk/co/squadlist/web/api/SquadlistApiFactory.java b/src/main/java/uk/co/squadlist/web/api/SquadlistApiFactory.java index e649c43e..6b037bae 100644 --- a/src/main/java/uk/co/squadlist/web/api/SquadlistApiFactory.java +++ b/src/main/java/uk/co/squadlist/web/api/SquadlistApiFactory.java @@ -7,22 +7,22 @@ @Component public class SquadlistApiFactory { - private final String apiUrl; - private final String clientAccessToken; - - @Autowired - public SquadlistApiFactory(@Value("${apiUrl}") String apiUrl, - @Value("${apiAccessToken}") String clientAccessToken) { - this.apiUrl = apiUrl; - this.clientAccessToken = clientAccessToken; - } - - public SquadlistApi createClient() { - return createForToken(clientAccessToken); - } + private final String apiUrl; + private final String clientAccessToken; - public SquadlistApi createForToken(String token) { - return new SquadlistApi(apiUrl, token); - } + @Autowired + public SquadlistApiFactory(@Value("${apiUrl}") String apiUrl, + @Value("${apiAccessToken}") String clientAccessToken) { + this.apiUrl = apiUrl; + this.clientAccessToken = clientAccessToken; + } + + public SquadlistApi createClient() { + return createForToken(clientAccessToken); + } + + public SquadlistApi createForToken(String token) { + return new SquadlistApi(apiUrl, token); + } } diff --git a/src/main/java/uk/co/squadlist/web/auth/LoggedInUserService.java b/src/main/java/uk/co/squadlist/web/auth/LoggedInUserService.java index 04674ed2..4c90dc7b 100644 --- a/src/main/java/uk/co/squadlist/web/auth/LoggedInUserService.java +++ b/src/main/java/uk/co/squadlist/web/auth/LoggedInUserService.java @@ -12,42 +12,42 @@ @Component public class LoggedInUserService { - private final static Logger log = Logger.getLogger(LoggedInUserService.class); - - private static final String SIGNED_IN_USER_ACCESS_TOKEN = "signedInAccessToken"; - - private final InstanceSpecificApiClient api; - private final HttpServletRequest request; - - @Autowired - public LoggedInUserService(InstanceSpecificApiClient api, HttpServletRequest request) { - this.api = api; - this.request = request; - } - - public Member getLoggedInMember() throws SignedInMemberRequiredException { - String token = getLoggedInMembersToken(); - if (token != null) { - log.debug("Found signed in user token; need to verify: " + token); - Member verifiedMember = api.verify(token); - log.debug("Verified member: "+ verifiedMember); - return verifiedMember; - } - - log.debug("No signed in user token found"); - throw new SignedInMemberRequiredException(); - } - - public String getLoggedInMembersToken() { - return (String) request.getSession().getAttribute(SIGNED_IN_USER_ACCESS_TOKEN); - } - - public void setSignedIn(String token) { - request.getSession().setAttribute(SIGNED_IN_USER_ACCESS_TOKEN, token); - } - - public void cleanSignedIn() { - request.getSession().removeAttribute(SIGNED_IN_USER_ACCESS_TOKEN); - } + private final static Logger log = Logger.getLogger(LoggedInUserService.class); + + private static final String SIGNED_IN_USER_ACCESS_TOKEN = "signedInAccessToken"; + + private final InstanceSpecificApiClient api; + private final HttpServletRequest request; + + @Autowired + public LoggedInUserService(InstanceSpecificApiClient api, HttpServletRequest request) { + this.api = api; + this.request = request; + } + + public Member getLoggedInMember() throws SignedInMemberRequiredException { + String token = getLoggedInMembersToken(); + if (token != null) { + log.debug("Found signed in user token; need to verify: " + token); + Member verifiedMember = api.verify(token); + log.debug("Verified member: " + verifiedMember); + return verifiedMember; + } + + log.debug("No signed in user token found"); + throw new SignedInMemberRequiredException(); + } + + public String getLoggedInMembersToken() { + return (String) request.getSession().getAttribute(SIGNED_IN_USER_ACCESS_TOKEN); + } + + public void setSignedIn(String token) { + request.getSession().setAttribute(SIGNED_IN_USER_ACCESS_TOKEN, token); + } + + public void cleanSignedIn() { + request.getSession().removeAttribute(SIGNED_IN_USER_ACCESS_TOKEN); + } } \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/controllers/AdminController.java b/src/main/java/uk/co/squadlist/web/controllers/AdminController.java deleted file mode 100644 index b857a185..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/AdminController.java +++ /dev/null @@ -1,201 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import com.google.common.base.Splitter; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import org.apache.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; -import uk.co.squadlist.web.annotations.RequiresPermission; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.context.Context; -import uk.co.squadlist.web.context.GoverningBodyFactory; -import uk.co.squadlist.web.exceptions.SignedInMemberRequiredException; -import uk.co.squadlist.web.model.Instance; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.model.forms.InstanceDetails; -import uk.co.squadlist.web.services.Permission; -import uk.co.squadlist.web.services.filters.ActiveMemberFilter; -import uk.co.squadlist.web.services.github.GithubService; -import uk.co.squadlist.web.urls.UrlBuilder; -import uk.co.squadlist.web.views.CsvOutputRenderer; -import uk.co.squadlist.web.views.DateFormatter; -import uk.co.squadlist.web.views.ViewFactory; - -import javax.servlet.http.HttpServletResponse; -import javax.validation.Valid; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -@Controller -public class AdminController { - - private final static Logger log = Logger.getLogger(AdminController.class); - - private static final List MEMBER_ORDERINGS = Lists.newArrayList("firstName", "lastName"); - private static final List GOVERNING_BODIES = Lists.newArrayList("british-rowing", "rowing-ireland"); - - private final static Splitter COMMA_SPLITTER = Splitter.on(","); - - private InstanceSpecificApiClient api; - private ViewFactory viewFactory; - private ActiveMemberFilter activeMemberFilter; - private CsvOutputRenderer csvOutputRenderer; - private UrlBuilder urlBuilder; - private GithubService githubService; - private Context context; - private DateFormatter dateFormatter; - private GoverningBodyFactory governingBodyFactory; - - public AdminController() { - } - - @Autowired - public AdminController(InstanceSpecificApiClient api, ViewFactory viewFactory, - ActiveMemberFilter activeMemberFilter, CsvOutputRenderer csvOutputRenderer, - UrlBuilder urlBuilder, GithubService githubService, - Context context, DateFormatter dateFormatter, GoverningBodyFactory governingBodyFactory) { - this.api = api; - this.viewFactory = viewFactory; - this.activeMemberFilter = activeMemberFilter; - this.csvOutputRenderer = csvOutputRenderer; - this.urlBuilder = urlBuilder; - this.githubService = githubService; - this.context = context; - this.dateFormatter = dateFormatter; - this.governingBodyFactory = governingBodyFactory; - } - - @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value = "/admin", method = RequestMethod.GET) - public ModelAndView member() throws Exception { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("admin"); - mv.addObject("squads", api.getSquads()); - mv.addObject("availabilityOptions", api.getAvailabilityOptions()); - mv.addObject("title", "Admin"); - - final List members = api.getMembers(); - mv.addObject("members", members); - mv.addObject("activeMembers", activeMemberFilter.extractActive(members)); - mv.addObject("inactiveMembers", activeMemberFilter.extractInactive(members)); - mv.addObject("admins", extractAdminUsersFrom(members)); - mv.addObject("governingBody", governingBodyFactory.getGoverningBody()); - mv.addObject("statistics", api.statistics()); - mv.addObject("boats", api.getBoats()); - - mv.addObject("openIssues", githubService.getOpenIssues()); - mv.addObject("closedIssues", githubService.getClosedIssues()); - mv.addObject("language", context.getLanguage()); - return mv; - } - - @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value = "/admin/instance", method = RequestMethod.GET) - public ModelAndView instance() throws Exception { - final InstanceDetails instanceDetails = new InstanceDetails(); - instanceDetails.setMemberOrdering(api.getInstance().getMemberOrdering()); - instanceDetails.setGoverningBody(api.getInstance().getGoverningBody()); - return renderEditInstanceDetailsForm(instanceDetails); - } - - @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value = "/admin/instance", method = RequestMethod.POST) - public ModelAndView instanceSubmit(@Valid @ModelAttribute("instanceDetails") InstanceDetails instanceDetails, BindingResult result) throws Exception { - if (result.hasErrors()) { - return renderEditInstanceDetailsForm(instanceDetails); - } - - Instance instance = api.getInstance(); - instance.setMemberOrdering(instanceDetails.getMemberOrdering()); // TODO validate - instance.setGoverningBody(instanceDetails.getGoverningBody()); // TODO validate - - api.updateInstance(instance); - - return redirectToAdminScreen(); - } - - @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value = "/admin/admins", method = RequestMethod.GET) - public ModelAndView setAdminsPrompt() throws Exception { - List adminMembers = Lists.newArrayList(); - List availableMembers = Lists.newArrayList(); - for (Member member : api.getMembers()) { - if (member.getAdmin()) { - adminMembers.add(member); - } else { - availableMembers.add(member); - } - } - return viewFactory.getViewForLoggedInUser("editAdmins").addObject("admins", adminMembers).addObject("availableMembers", availableMembers); - } - - @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value = "/admin/admins", method = RequestMethod.POST) - public ModelAndView setAdmins(@RequestParam String admins) throws Exception { - log.info("Setting admins request: " + admins); - final Set updatedAdmins = Sets.newHashSet(COMMA_SPLITTER.split(admins).iterator()); - - log.info("Setting admins to: " + updatedAdmins); - api.setAdmins(updatedAdmins); - - return redirectToAdminScreen(); - } - - @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value = "/admin/export/members.csv", method = RequestMethod.GET) - public void membersCSV(HttpServletResponse response) throws Exception { - final List> rows = Lists.newArrayList(); - for (Member member : api.getMembers()) { - rows.add(Arrays.asList(member.getFirstName(), - member.getLastName(), - member.getKnownAs(), - member.getEmailAddress(), - member.getGender(), - member.getDateOfBirth() != null ? dateFormatter.dayMonthYear(member.getDateOfBirth()) : "", - member.getEmergencyContactName(), - member.getEmergencyContactNumber(), - member.getWeight() != null ? member.getWeight().toString() : "", - member.getSweepOarSide(), - member.getSculling(), - member.getRegistrationNumber(), - member.getRowingPoints(), - member.getScullingPoints(), - member.getRole() - )); - } - - csvOutputRenderer.renderCsvResponse(response, Lists.newArrayList("First name", "Last name", "Known as", "Email", - "Gender", "Date of birth", "Emergency contact name", "Emergency contact number", - "Weight", "Sweep oar side", "Sculling", "Registration number", "Rowing points", "Sculling points", "Role"), rows); - } - - private List extractAdminUsersFrom(List members) { - List admins = Lists.newArrayList(); - for (Member member : members) { - if (member.getAdmin() != null && member.getAdmin()) { // TODO should be boolean is the API knows that it is always present. - admins.add(member); - } - } - return admins; - } - - private ModelAndView redirectToAdminScreen() { - return new ModelAndView(new RedirectView(urlBuilder.adminUrl())); - } - - private ModelAndView renderEditInstanceDetailsForm(final InstanceDetails instanceDetails) throws SignedInMemberRequiredException { - return viewFactory.getViewForLoggedInUser("editInstance"). - addObject("instanceDetails", instanceDetails). - addObject("memberOrderings", MEMBER_ORDERINGS). - addObject("governingBodies", GOVERNING_BODIES); - } - -} \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/controllers/AvailabilityController.java b/src/main/java/uk/co/squadlist/web/controllers/AvailabilityController.java deleted file mode 100644 index 65ff2dc8..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/AvailabilityController.java +++ /dev/null @@ -1,104 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import java.util.Date; -import java.util.List; -import java.util.Map; - -import org.joda.time.DateTime; -import org.joda.time.format.ISODateTimeFormat; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; - -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.api.SquadlistApi; -import uk.co.squadlist.web.api.SquadlistApiFactory; -import uk.co.squadlist.web.model.AvailabilityOption; -import uk.co.squadlist.web.model.Outing; -import uk.co.squadlist.web.model.OutingWithSquadAvailability; -import uk.co.squadlist.web.model.Squad; -import uk.co.squadlist.web.services.PreferedSquadService; -import uk.co.squadlist.web.services.filters.ActiveMemberFilter; -import uk.co.squadlist.web.views.DateHelper; -import uk.co.squadlist.web.views.ViewFactory; - -import com.google.common.collect.Maps; - -@Controller -public class AvailabilityController { - - private InstanceSpecificApiClient instanceSpecificApiClient; - private PreferedSquadService preferedSquadService; - private ViewFactory viewFactory; - private ActiveMemberFilter activeMemberFilter; - private SquadlistApi squadlistApi; - - @Autowired - public AvailabilityController(InstanceSpecificApiClient instanceSpecificApiClient, PreferedSquadService preferedSquadService, ViewFactory viewFactory, - ActiveMemberFilter activeMemberFilter, SquadlistApiFactory squadlistApiFactory) { - this.instanceSpecificApiClient = instanceSpecificApiClient; - this.preferedSquadService = preferedSquadService; - this.viewFactory = viewFactory; - this.activeMemberFilter = activeMemberFilter; - this.squadlistApi = squadlistApiFactory.createClient(); - } - - @RequestMapping("/availability") - public ModelAndView availability() throws Exception { - ModelAndView mv = viewFactory.getViewForLoggedInUser("availability"); - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - return mv; - } - - @RequestMapping("/availability/{squadId}") - public ModelAndView squadAvailability(@PathVariable String squadId, @RequestParam(value = "month", required = false) String month) throws Exception { - ModelAndView mv = viewFactory.getViewForLoggedInUser("availability"); - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - - final Squad squad = preferedSquadService.resolveSquad(squadId); - - if (squad != null) { - mv.addObject("squad", squad); - mv.addObject("title", squad.getName() + " availability"); - mv.addObject("members", activeMemberFilter.extractActive(squadlistApi.getSquadMembers(squad.getId()))); - - if (squadlistApi.getSquadMembers(squad.getId()).isEmpty()) { - return mv; - } - - Date startDate = DateHelper.startOfCurrentOutingPeriod().toDate(); - Date endDate = DateHelper.endOfCurrentOutingPeriod().toDate(); - if (month != null) { - final DateTime monthDateTime = ISODateTimeFormat.yearMonth().parseDateTime(month); // TODO Can be moved to spring? - startDate = monthDateTime.toDate(); - endDate = monthDateTime.plusMonths(1).toDate(); - } else { - mv.addObject("current", true); - } - - final List squadAvailability = squadlistApi.getSquadAvailability(squad.getId(), startDate, endDate); - final List outings = instanceSpecificApiClient.getSquadOutings(squad, startDate, endDate); - - mv.addObject("squadAvailability", decorateOutingsWithMembersAvailability(squadAvailability, outings)); - mv.addObject("outings", outings); - mv.addObject("outingMonths", instanceSpecificApiClient.getOutingMonths(squad)); - mv.addObject("month", month); - } - return mv; - } - - private Map decorateOutingsWithMembersAvailability(final List squadAvailability, final List outings) { - final Map allAvailability = Maps.newHashMap(); - for (OutingWithSquadAvailability outingWithSquadAvailability : squadAvailability) { - final Map outingAvailability = outingWithSquadAvailability.getAvailability(); - for (String member : outingAvailability.keySet()) { - allAvailability.put(outingWithSquadAvailability.getOuting().getId() + "-" + member, outingAvailability.get(member)); - } - } - return allAvailability; - } - -} diff --git a/src/main/java/uk/co/squadlist/web/controllers/AvailabilityOptionsController.java b/src/main/java/uk/co/squadlist/web/controllers/AvailabilityOptionsController.java deleted file mode 100644 index dc0ac325..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/AvailabilityOptionsController.java +++ /dev/null @@ -1,162 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import java.io.IOException; -import java.util.List; - -import javax.validation.Valid; - -import org.apache.log4j.Logger; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.map.JsonMappingException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; - -import uk.co.eelpieconsulting.common.http.HttpFetchException; -import uk.co.squadlist.web.annotations.RequiresPermission; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.exceptions.InvalidAvailabilityOptionException; -import uk.co.squadlist.web.exceptions.SignedInMemberRequiredException; -import uk.co.squadlist.web.exceptions.UnknownAvailabilityOptionException; -import uk.co.squadlist.web.model.AvailabilityOption; -import uk.co.squadlist.web.model.forms.AvailabilityOptionDetails; -import uk.co.squadlist.web.services.Permission; -import uk.co.squadlist.web.urls.UrlBuilder; -import uk.co.squadlist.web.views.ViewFactory; - -import com.google.common.base.Strings; - -@Controller -public class AvailabilityOptionsController { - - private final static Logger log = Logger.getLogger(AvailabilityOptionsController.class); - - private InstanceSpecificApiClient api; - private ViewFactory viewFactory; - private UrlBuilder urlBuilder; - - public AvailabilityOptionsController() { - } - - @Autowired - public AvailabilityOptionsController(InstanceSpecificApiClient api, ViewFactory viewFactory, UrlBuilder urlBuilder) { - this.api = api; - this.viewFactory = viewFactory; - this.urlBuilder = urlBuilder; - } - - @RequiresPermission(permission=Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value="/availability-option/{id}/edit", method=RequestMethod.GET) - public ModelAndView editPrompt(@PathVariable String id) throws JsonParseException, JsonMappingException, HttpFetchException, IOException, UnknownAvailabilityOptionException, SignedInMemberRequiredException { - final AvailabilityOption a = api.getAvailabilityOption(id); - - final AvailabilityOptionDetails availabilityOption = new AvailabilityOptionDetails(); - availabilityOption.setName(a.getLabel()); - availabilityOption.setColour(a.getColour()); - - return renderEditAvailabilityOptionForm(availabilityOption, a); - } - - @RequiresPermission(permission=Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value="/availability-option/{id}/delete", method=RequestMethod.GET) - public ModelAndView deletePrompt(@PathVariable String id) throws JsonParseException, JsonMappingException, HttpFetchException, IOException, UnknownAvailabilityOptionException, SignedInMemberRequiredException { - final AvailabilityOption a = api.getAvailabilityOption(id); - return renderDeleteForm(a); - } - - @RequiresPermission(permission=Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value="/availability-option/{id}/delete", method=RequestMethod.POST) - public ModelAndView delete(@PathVariable String id, @RequestParam(required=false) String alternative) throws JsonParseException, JsonMappingException, HttpFetchException, IOException, UnknownAvailabilityOptionException { - final AvailabilityOption a = api.getAvailabilityOption(id); - - if (!Strings.isNullOrEmpty(alternative)) { - final AvailabilityOption alternativeOption = api.getAvailabilityOption(alternative); - log.info("Deleting availability option: " + a + " replacing with: " + alternativeOption); - api.deleteAvailabilityOption(a, alternativeOption); - - } else { - log.info("Deleting availability option: " + a); - api.deleteAvailabilityOption(a); - } - - return redirectToAdmin(); - } - - @RequiresPermission(permission=Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value="/availability-option/new", method=RequestMethod.GET) - public ModelAndView availability() throws SignedInMemberRequiredException { - AvailabilityOptionDetails availabilityOption = new AvailabilityOptionDetails(); - availabilityOption.setColour("green"); - return renderNewAvailabilityOptionForm(availabilityOption); - } - - @RequiresPermission(permission=Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value="/availability-option/new", method=RequestMethod.POST) - public ModelAndView newSquadSubmit(@Valid @ModelAttribute("availabilityOptionDetails") AvailabilityOptionDetails availabilityOptionDetails, BindingResult result) throws SignedInMemberRequiredException { - if (result.hasErrors()) { - return renderNewAvailabilityOptionForm(availabilityOptionDetails); - } - - try { - api.createAvailabilityOption(availabilityOptionDetails.getName(), availabilityOptionDetails.getColour()); - return redirectToAdmin(); - - } catch (InvalidAvailabilityOptionException e) { - result.rejectValue("name", null, e.getMessage()); - return renderNewAvailabilityOptionForm(availabilityOptionDetails); - } - } - - @RequiresPermission(permission=Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value="/availability-option/{id}/edit", method=RequestMethod.POST) - public ModelAndView editPost(@PathVariable String id, @Valid @ModelAttribute("availabilityOptionDetails") AvailabilityOptionDetails availabilityOptionDetails, BindingResult result) throws JsonParseException, JsonMappingException, HttpFetchException, IOException, UnknownAvailabilityOptionException, SignedInMemberRequiredException { - final AvailabilityOption a = api.getAvailabilityOption(id); - if (result.hasErrors()) { - return renderEditAvailabilityOptionForm(availabilityOptionDetails, a); - } - - a.setLabel(availabilityOptionDetails.getName()); - a.setColour(availabilityOptionDetails.getColour()); - - try { - api.updateAvailabilityOption(a); - - } catch (InvalidAvailabilityOptionException e) { - result.rejectValue("name", null, e.getMessage()); - return renderEditAvailabilityOptionForm(availabilityOptionDetails, a); - } - - return redirectToAdmin(); - } - - private ModelAndView renderNewAvailabilityOptionForm(AvailabilityOptionDetails availabilityOptionDetails) throws SignedInMemberRequiredException { - return viewFactory.getViewForLoggedInUser("newAvailabilityOption").addObject("availabilityOptionDetails", availabilityOptionDetails); - } - - private ModelAndView renderEditAvailabilityOptionForm(AvailabilityOptionDetails availabilityOptionDetails, AvailabilityOption a) throws SignedInMemberRequiredException { - return viewFactory.getViewForLoggedInUser("editAvailabilityOption"). - addObject("availabilityOptionDetails", availabilityOptionDetails). - addObject("availabilityOption", a); - } - - private ModelAndView redirectToAdmin() { - return new ModelAndView(new RedirectView(urlBuilder.adminUrl())); - } - - private ModelAndView renderDeleteForm(final AvailabilityOption a) throws JsonParseException, JsonMappingException, HttpFetchException, IOException, SignedInMemberRequiredException { - final List alternatives = api.getAvailabilityOptions(); - alternatives.remove(a); - - return viewFactory.getViewForLoggedInUser("deleteAvailabilityOption"). - addObject("availabilityOption", a). - addObject("alternatives", alternatives); - } - -} \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/controllers/BoatsController.java b/src/main/java/uk/co/squadlist/web/controllers/BoatsController.java deleted file mode 100644 index 80b98113..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/BoatsController.java +++ /dev/null @@ -1,32 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; - -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.views.ViewFactory; - -@Controller -public class BoatsController { - - private InstanceSpecificApiClient api; - private ViewFactory viewFactory; - - public BoatsController() { - } - - @Autowired - public BoatsController(InstanceSpecificApiClient api, ViewFactory viewFactory) { - this.api = api; - this.viewFactory = viewFactory; - } - - @RequestMapping("/boats/{id}") - public ModelAndView outing(@PathVariable String id) throws Exception { - return viewFactory.getViewForLoggedInUser("boat").addObject("boat", api.getBoat(id)); - } - -} diff --git a/src/main/java/uk/co/squadlist/web/controllers/ContactsController.java b/src/main/java/uk/co/squadlist/web/controllers/ContactsController.java deleted file mode 100644 index bb07dbde..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/ContactsController.java +++ /dev/null @@ -1,60 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import java.util.List; - -import org.apache.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; - -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.model.Squad; -import uk.co.squadlist.web.services.PreferedSquadService; -import uk.co.squadlist.web.views.ViewFactory; - -@Controller -public class ContactsController { - - private static Logger log = Logger.getLogger(ContactsController.class); - - private InstanceSpecificApiClient api; - private PreferedSquadService preferedSquadService; - private ViewFactory viewFactory; - private ContactsModelPopulator contactsModelPopulator; - - public ContactsController() { - } - - @Autowired - public ContactsController(InstanceSpecificApiClient api, PreferedSquadService preferedSquadService, ViewFactory viewFactory, ContactsModelPopulator contactsModelPopulator) { - this.api = api; - this.preferedSquadService = preferedSquadService; - this.viewFactory = viewFactory; - this.contactsModelPopulator = contactsModelPopulator; - } - - @RequestMapping("/contacts") - public ModelAndView contacts() throws Exception { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("contacts"); - final List allSquads = api.getSquads(); - mv.addObject("squads", allSquads); // TODO leaves squad null on view - return mv; - } - - @RequestMapping("/contacts/{squadId}") - public ModelAndView squadContacts(@PathVariable String squadId) throws Exception { - final Squad squadToShow = preferedSquadService.resolveSquad(squadId); - - final ModelAndView mv = viewFactory.getViewForLoggedInUser("contacts"); - final List allSquads = api.getSquads(); - mv.addObject("squads", allSquads); - if (!allSquads.isEmpty()) { - log.info("Squad to show: " + squadToShow); - contactsModelPopulator.populateModel(squadToShow, mv); - } - return mv; - } - -} diff --git a/src/main/java/uk/co/squadlist/web/controllers/ContactsModelPopulator.java b/src/main/java/uk/co/squadlist/web/controllers/ContactsModelPopulator.java deleted file mode 100644 index 0dd19233..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/ContactsModelPopulator.java +++ /dev/null @@ -1,114 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import java.util.List; -import java.util.Set; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.ModelAndView; - -import uk.co.squadlist.web.annotations.RequiresSquadPermission; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.api.SquadlistApi; -import uk.co.squadlist.web.api.SquadlistApiFactory; -import uk.co.squadlist.web.auth.LoggedInUserService; -import uk.co.squadlist.web.exceptions.SignedInMemberRequiredException; -import uk.co.squadlist.web.exceptions.UnknownInstanceException; -import uk.co.squadlist.web.exceptions.UnknownMemberException; -import uk.co.squadlist.web.model.Instance; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.model.Squad; -import uk.co.squadlist.web.services.Permission; -import uk.co.squadlist.web.services.PermissionsService; -import uk.co.squadlist.web.services.filters.ActiveMemberFilter; - -import com.google.common.base.Function; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.collect.Ordering; -import com.google.common.collect.Sets; - -@Component -public class ContactsModelPopulator { - - private static Function roleName = new Function() { - @Override - public String apply(Member obj) { - return obj.getRole(); - } - }; - - public ContactsModelPopulator() { - } - - private static Function firstName = new Function() { - @Override - public String apply(Member obj) { - return obj.getFirstName(); - } - }; - - private static Function lastName = new Function() { - @Override - public String apply(Member obj) { - return obj.getLastName(); - } - }; - - private final static Ordering byLastName = Ordering.natural().nullsLast().onResultOf(lastName); - private final static Ordering byFirstName = Ordering.natural().nullsLast().onResultOf(firstName); - private final static Ordering byRole = Ordering.natural().nullsLast().onResultOf(roleName); - private final static Ordering byRoleThenFirstName = byRole.compound(byFirstName); - private final static Ordering byRoleThenLastName = byRole.compound(byLastName); - - private InstanceSpecificApiClient instanceSpecificApiClient; - private LoggedInUserService loggedInUserService; - private PermissionsService permissionsService; - private ActiveMemberFilter activeMemberFilter; - private SquadlistApi squadlistApi; - - @Autowired - public ContactsModelPopulator(InstanceSpecificApiClient instanceSpecificApiClient, LoggedInUserService loggedInUserService, PermissionsService permissionsService, - ActiveMemberFilter activeMemberFilter, SquadlistApiFactory squadlistApiFactory) { - this.instanceSpecificApiClient = instanceSpecificApiClient; - this.loggedInUserService = loggedInUserService; - this.permissionsService = permissionsService; - this.activeMemberFilter = activeMemberFilter; - this.squadlistApi = squadlistApiFactory.createClient(); - } - - @RequiresSquadPermission(permission=Permission.VIEW_SQUAD_CONTACT_DETAILS) - public void populateModel(final Squad squad, final ModelAndView mv) throws UnknownMemberException, UnknownInstanceException, SignedInMemberRequiredException { - mv.addObject("title", squad.getName() + " contacts"); - mv.addObject("squad", squad); - - Instance instance = instanceSpecificApiClient.getInstance(); - Ordering byRoleThenName = instance.getMemberOrdering() != null && instance.getMemberOrdering().equals("firstName") ? byRoleThenFirstName : byRoleThenLastName; - - final List activeMembers = byRoleThenName.sortedCopy(activeMemberFilter.extractActive(squadlistApi.getSquadMembers(squad.getId()))); - final List redactedMembers = redactContentDetailsForMembers(loggedInUserService.getLoggedInMember(), activeMembers); - mv.addObject("members", redactedMembers); - - final Set emails = Sets.newHashSet(); - for (Member member : redactedMembers) { - if (!Strings.isNullOrEmpty(member.getEmailAddress())) { - emails.add(member.getEmailAddress()); - } - } - if (!emails.isEmpty()) { - mv.addObject("emails", Lists.newArrayList(emails)); - } - } - - private List redactContentDetailsForMembers(Member loggedInMember, List members) { - List redactedMembers = Lists.newArrayList(); - for (Member member : members) { - if (!permissionsService.canSeePhoneNumberForRower(loggedInMember, member)) { - member.setContactNumber(null); - } - redactedMembers.add(member); - } - return redactedMembers; - } - -} \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/controllers/EntryDetailsController.java b/src/main/java/uk/co/squadlist/web/controllers/EntryDetailsController.java deleted file mode 100644 index 51cb2553..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/EntryDetailsController.java +++ /dev/null @@ -1,146 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import com.google.common.base.Splitter; -import com.google.common.collect.Lists; -import org.apache.log4j.Logger; -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.map.ObjectMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.ModelAndView; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.api.SquadlistApi; -import uk.co.squadlist.web.api.SquadlistApiFactory; -import uk.co.squadlist.web.auth.LoggedInUserService; -import uk.co.squadlist.web.context.GoverningBodyFactory; -import uk.co.squadlist.web.localisation.GoverningBody; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.model.Squad; -import uk.co.squadlist.web.services.PreferedSquadService; -import uk.co.squadlist.web.views.CsvOutputRenderer; -import uk.co.squadlist.web.views.ViewFactory; - -import javax.servlet.http.HttpServletResponse; -import java.util.Date; -import java.util.Iterator; -import java.util.List; - -@Controller -public class EntryDetailsController { - - private final static Logger log = Logger.getLogger(EntryDetailsController.class); - - private final InstanceSpecificApiClient instanceSpecificApiClient; - private final PreferedSquadService preferedSquadService; - private final ViewFactory viewFactory; - private final EntryDetailsModelPopulator entryDetailsModelPopulator; - private final CsvOutputRenderer csvOutputRenderer; - private final GoverningBodyFactory governingBodyFactory; - private final SquadlistApi squadlistApi; - private final LoggedInUserService loggedInUserService; - private final SquadlistApiFactory squadlistApiFactory; - - @Autowired - public EntryDetailsController(InstanceSpecificApiClient instanceSpecificApiClient, PreferedSquadService preferedSquadService, ViewFactory viewFactory, - EntryDetailsModelPopulator entryDetailsModelPopulator, - CsvOutputRenderer csvOutputRenderer, GoverningBodyFactory governingBodyFactory, - SquadlistApiFactory squadlistApiFactory, LoggedInUserService loggedInUserService) { - this.instanceSpecificApiClient = instanceSpecificApiClient; - this.preferedSquadService = preferedSquadService; - this.viewFactory = viewFactory; - this.entryDetailsModelPopulator = entryDetailsModelPopulator; - this.csvOutputRenderer = csvOutputRenderer; - this.governingBodyFactory = governingBodyFactory; - this.loggedInUserService = loggedInUserService; - this.squadlistApiFactory = squadlistApiFactory; - this.squadlistApi = squadlistApiFactory.createClient(); - } - - @RequestMapping("/entrydetails/{squadId}") - public ModelAndView entrydetails(@PathVariable String squadId) throws Exception { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("entryDetails"); - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - mv.addObject("governingBody", governingBodyFactory.getGoverningBody()); - - final Squad squadToShow = preferedSquadService.resolveSquad(squadId); - entryDetailsModelPopulator.populateModel(squadToShow, mv); - return mv; - } - - @RequestMapping("/entrydetails/ajax") - public ModelAndView ajax(@RequestBody String json) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - List selectedMembers = Lists.newArrayList(); - - JsonNode readTree = new ObjectMapper().readTree(json); - Iterator iterator = readTree.iterator(); - while (iterator.hasNext()) { - selectedMembers.add(loggedInUserApi.getMember(iterator.next().asText())); - } - - List rowingPoints = Lists.newArrayList(); - List scullingPoints = Lists.newArrayList(); - for (Member member : selectedMembers) { - rowingPoints.add(member.getRowingPoints()); - scullingPoints.add(member.getScullingPoints()); - } - - final ModelAndView mv = viewFactory.getViewForLoggedInUser("entryDetailsAjax"); - if (!selectedMembers.isEmpty()) { - mv.addObject("members", selectedMembers); - - final GoverningBody governingBody = governingBodyFactory.getGoverningBody(); - - int crewSize = selectedMembers.size(); - final boolean isFullBoat = governingBody.getBoatSizes().contains(crewSize); - mv.addObject("ok", isFullBoat); - if (isFullBoat) { - mv.addObject("rowingPoints", governingBody.getTotalPoints(rowingPoints)); - mv.addObject("rowingStatus", governingBody.getRowingStatus(rowingPoints)); - - mv.addObject("scullingPoints", governingBody.getTotalPoints(scullingPoints)); - mv.addObject("scullingStatus", governingBody.getScullingStatus(scullingPoints)); - - List datesOfBirth = Lists.newArrayList(); - for (Member member : selectedMembers) { - datesOfBirth.add(member.getDateOfBirth()); - } - - Integer effectiveAge = governingBody.getEffectiveAge(datesOfBirth); - if (effectiveAge != null) { - mv.addObject("effectiveAge", effectiveAge); - mv.addObject("ageGrade", governingBody.getAgeGrade(effectiveAge)); - } - } - } - return mv; - } - - @RequestMapping(value = "/entrydetails/{squadId}.csv", method = RequestMethod.GET) - public void entrydetailsCSV(@PathVariable String squadId, HttpServletResponse response) throws Exception { - viewFactory.getViewForLoggedInUser("entryDetails"); // TODO - - final Squad squadToShow = preferedSquadService.resolveSquad(squadId); - final List squadMembers = squadlistApi.getSquadMembers(squadToShow.getId()); - - List> entryDetailsRows = entryDetailsModelPopulator.getEntryDetailsRows(squadMembers); - csvOutputRenderer.renderCsvResponse(response, entryDetailsModelPopulator.getEntryDetailsHeaders(), entryDetailsRows); - } - - @RequestMapping(value = "/entrydetails/selected.csv", method = RequestMethod.GET) // TODO Unused - public void entrydetailsSelectedCSV(@RequestParam String members, HttpServletResponse response) throws Exception { - log.info("Selected members: " + members); - List selectedMembers = Lists.newArrayList(); - final Iterator iterator = Splitter.on(",").split(members).iterator(); - while (iterator.hasNext()) { - final String selectedMemberId = iterator.next(); - log.info("Selected member id: " + selectedMemberId); - selectedMembers.add(squadlistApi.getMember(selectedMemberId)); - } - - csvOutputRenderer.renderCsvResponse(response, entryDetailsModelPopulator.getEntryDetailsHeaders(), entryDetailsModelPopulator.getEntryDetailsRows(selectedMembers)); - } - -} \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/controllers/EntryDetailsModelPopulator.java b/src/main/java/uk/co/squadlist/web/controllers/EntryDetailsModelPopulator.java deleted file mode 100644 index 65f66ed6..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/EntryDetailsModelPopulator.java +++ /dev/null @@ -1,84 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import java.util.Arrays; -import java.util.List; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.ModelAndView; - -import uk.co.squadlist.web.annotations.RequiresSquadPermission; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.api.SquadlistApi; -import uk.co.squadlist.web.api.SquadlistApiFactory; -import uk.co.squadlist.web.context.GoverningBodyFactory; -import uk.co.squadlist.web.localisation.GoverningBody; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.model.Squad; -import uk.co.squadlist.web.services.Permission; -import uk.co.squadlist.web.services.filters.ActiveMemberFilter; -import uk.co.squadlist.web.views.DateFormatter; - -import com.google.common.collect.Lists; - -@Component -public class EntryDetailsModelPopulator { - - private DateFormatter dateFormatter; - private ActiveMemberFilter activeMemberFilter; - private GoverningBodyFactory governingBodyFactory; - private SquadlistApi squadlistApi; - - public EntryDetailsModelPopulator() { - } - - @Autowired - public EntryDetailsModelPopulator(DateFormatter dateFormatter, ActiveMemberFilter activeMemberFilter, - GoverningBodyFactory governingBodyFactory, SquadlistApiFactory squadlistApiFactory) { - this.dateFormatter = dateFormatter; - this.activeMemberFilter = activeMemberFilter; - this.governingBodyFactory = governingBodyFactory; - this.squadlistApi = squadlistApiFactory.createClient(); - } - - @RequiresSquadPermission(permission=Permission.VIEW_SQUAD_ENTRY_DETAILS) - public void populateModel(final Squad squadToShow, final ModelAndView mv) { - mv.addObject("squad", squadToShow); - mv.addObject("title", squadToShow.getName() + " entry details"); - mv.addObject("members", activeMemberFilter.extractActive(squadlistApi.getSquadMembers(squadToShow.getId()))); - } - - @RequiresSquadPermission(permission=Permission.VIEW_SQUAD_ENTRY_DETAILS) - public List> getEntryDetailsRows(Squad squadToShow) { - return getEntryDetailsRows(activeMemberFilter.extractActive(squadlistApi.getSquadMembers(squadToShow.getId()))); - } - - public List> getEntryDetailsRows(List members) { // TOOD permissions - final GoverningBody governingBody = governingBodyFactory.getGoverningBody(); - - final List> rows = Lists.newArrayList(); - for (Member member : members) { - final Integer effectiveAge = member.getDateOfBirth() != null ? governingBody.getEffectiveAge(member.getDateOfBirth()) : null; - final String ageGrade = effectiveAge != null ? governingBody.getAgeGrade(effectiveAge) : null; - - rows.add(Arrays.asList(member.getFirstName(), member.getLastName(), - member.getDateOfBirth() != null ? dateFormatter.dayMonthYear(member.getDateOfBirth()) : "", - effectiveAge != null ? effectiveAge.toString() : "", - ageGrade != null ? ageGrade : "", - member.getWeight() != null ? member.getWeight().toString() : "", - member.getRowingPoints(), - governingBody.getRowingStatus(member.getRowingPoints()), - member.getScullingPoints(), - governingBody.getScullingStatus(member.getScullingPoints()), - member.getRegistrationNumber())); - } - return rows; - } - - public List getEntryDetailsHeaders() { - return Lists.newArrayList("First name", "Last name", "Date of birth", "Effective age", "Age grade", - "Weight", "Rowing points", "Rowing status", - "Sculling points", "Sculling status", "Registration number"); - } - -} diff --git a/src/main/java/uk/co/squadlist/web/controllers/ErrorController.java b/src/main/java/uk/co/squadlist/web/controllers/ErrorController.java index 7d899e60..e9682b72 100644 --- a/src/main/java/uk/co/squadlist/web/controllers/ErrorController.java +++ b/src/main/java/uk/co/squadlist/web/controllers/ErrorController.java @@ -8,8 +8,8 @@ import javax.servlet.http.HttpServletRequest; -@Controller -public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController { +//@Controller +public class ErrorController { private final static Logger log = Logger.getLogger(ErrorController.class); @@ -26,7 +26,6 @@ public ModelAndView error() throws Exception { return new ModelAndView("404"); } - @Override public String getErrorPath() { return "/error"; } diff --git a/src/main/java/uk/co/squadlist/web/controllers/ExceptionHandler.java b/src/main/java/uk/co/squadlist/web/controllers/ExceptionHandler.java index 4b60aefe..300efc61 100644 --- a/src/main/java/uk/co/squadlist/web/controllers/ExceptionHandler.java +++ b/src/main/java/uk/co/squadlist/web/controllers/ExceptionHandler.java @@ -18,73 +18,73 @@ @Component public class ExceptionHandler implements HandlerExceptionResolver, Ordered { - private final static Logger log = Logger.getLogger(ExceptionHandler.class); - - private final UrlBuilder urlBuilder; - - @Value("${googleAnalyticsAccount}") - private String googleAnalyticsAccount; - - @Autowired - public ExceptionHandler(UrlBuilder urlBuilder) { - this.urlBuilder = urlBuilder; - } - - @Override - public ModelAndView resolveException(HttpServletRequest request, - HttpServletResponse response, - Object handler, - Exception e) { - - log.info("Handling exception of type: " + e.getClass()); - - if (e instanceof UnknownInstanceException) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return new ModelAndView("unknownInstance"); - } - - if (e instanceof UnknownAvailabilityOptionException) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return new ModelAndView("404"); - } - if (e instanceof UnknownBoatException) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return new ModelAndView("404"); - } - if (e instanceof UnknownInstanceException) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return new ModelAndView("404"); - } - if (e instanceof UnknownOutingException) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return new ModelAndView("404"); - } - if (e instanceof UnknownSquadException) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return new ModelAndView("404"); - } - if (e instanceof UnknownMemberException) { - response.setStatus(HttpStatus.NOT_FOUND.value()); - return new ModelAndView("404"); - } - - if (e instanceof PermissionDeniedException) { - response.setStatus(HttpStatus.FORBIDDEN.value()); - return new ModelAndView("403"); - } - - if (e instanceof SignedInMemberRequiredException) { - return new ModelAndView(new RedirectView(urlBuilder.loginUrl())); - } - - response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - log.error("Returning 500 error", e); - return new ModelAndView("500").addObject("googleAnalyticsAccount", googleAnalyticsAccount); - } - - @Override - public int getOrder() { - return Integer.MIN_VALUE; - } + private final static Logger log = Logger.getLogger(ExceptionHandler.class); + + private final UrlBuilder urlBuilder; + + @Value("${googleAnalyticsAccount}") + private String googleAnalyticsAccount; + + @Autowired + public ExceptionHandler(UrlBuilder urlBuilder) { + this.urlBuilder = urlBuilder; + } + + @Override + public ModelAndView resolveException(HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception e) { + + log.info("Handling exception of type: " + e.getClass()); + + if (e instanceof UnknownInstanceException) { + response.setStatus(HttpStatus.NOT_FOUND.value()); + return new ModelAndView("unknownInstance"); + } + + if (e instanceof UnknownAvailabilityOptionException) { + response.setStatus(HttpStatus.NOT_FOUND.value()); + return new ModelAndView("404"); + } + if (e instanceof UnknownBoatException) { + response.setStatus(HttpStatus.NOT_FOUND.value()); + return new ModelAndView("404"); + } + if (e instanceof UnknownInstanceException) { + response.setStatus(HttpStatus.NOT_FOUND.value()); + return new ModelAndView("404"); + } + if (e instanceof UnknownOutingException) { + response.setStatus(HttpStatus.NOT_FOUND.value()); + return new ModelAndView("404"); + } + if (e instanceof UnknownSquadException) { + response.setStatus(HttpStatus.NOT_FOUND.value()); + return new ModelAndView("404"); + } + if (e instanceof UnknownMemberException) { + response.setStatus(HttpStatus.NOT_FOUND.value()); + return new ModelAndView("404"); + } + + if (e instanceof PermissionDeniedException) { + response.setStatus(HttpStatus.FORBIDDEN.value()); + return new ModelAndView("403"); + } + + if (e instanceof SignedInMemberRequiredException) { + return new ModelAndView(new RedirectView(urlBuilder.loginUrl())); + } + + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + log.error("Returning 500 error", e); + return new ModelAndView("500").addObject("googleAnalyticsAccount", googleAnalyticsAccount); + } + + @Override + public int getOrder() { + return Integer.MIN_VALUE; + } } diff --git a/src/main/java/uk/co/squadlist/web/controllers/GoverningBodyController.java b/src/main/java/uk/co/squadlist/web/controllers/GoverningBodyController.java deleted file mode 100644 index a705823b..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/GoverningBodyController.java +++ /dev/null @@ -1,41 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.servlet.ModelAndView; - -import uk.co.squadlist.web.context.GoverningBodyFactory; -import uk.co.squadlist.web.localisation.GoverningBody; -import uk.co.squadlist.web.views.ViewFactory; - -@Controller -public class GoverningBodyController { - - private GoverningBodyFactory governingBodyFactory; - private ViewFactory viewFactory; - - public GoverningBodyController() { - } - - @Autowired - public GoverningBodyController(GoverningBodyFactory governingBodyFactory, ViewFactory viewFactory) { - this.governingBodyFactory = governingBodyFactory; - this.viewFactory = viewFactory; - } - - @RequestMapping(value="/governing-body/british-rowing", method=RequestMethod.GET) - public ModelAndView member() throws Exception { - final GoverningBody governingBody = governingBodyFactory.getGoverningBody(); - - final ModelAndView mv = viewFactory.getViewForLoggedInUser("governingBody"); - mv.addObject("governingBody", governingBody); - mv.addObject("title", governingBody.getName()); - mv.addObject("ageGrades", governingBody.getAgeGrades()); - mv.addObject("statuses", governingBody.getStatusPoints()); - mv.addObject("boatSizes", governingBody.getBoatSizes()); - return mv; - } - -} \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/controllers/LoginController.java b/src/main/java/uk/co/squadlist/web/controllers/LoginController.java deleted file mode 100644 index 7538fced..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/LoginController.java +++ /dev/null @@ -1,74 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import org.apache.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.auth.LoggedInUserService; -import uk.co.squadlist.web.exceptions.UnknownInstanceException; -import uk.co.squadlist.web.model.Instance; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.urls.UrlBuilder; - -@Controller -public class LoginController { - - private final static Logger log = Logger.getLogger(LoginController.class); - - private final InstanceSpecificApiClient api; - private final LoggedInUserService loggedInUserService; - private final UrlBuilder urlBuilder; - - @Autowired - public LoginController(InstanceSpecificApiClient api, LoggedInUserService loggedInUserService, UrlBuilder urlBuilder) { - this.api = api; - this.loggedInUserService = loggedInUserService; - this.urlBuilder = urlBuilder; - } - - @RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.HEAD}) // TODO SEO this onto the root url - public ModelAndView login() throws Exception { - return renderLoginScreen(false, null); - } - - @RequestMapping(value = "/login", method = {RequestMethod.POST}) - public ModelAndView loginSubmit( - @RequestParam(value = "username", required = true) String username, - @RequestParam(value = "password", required = true) String password) throws Exception { - - log.info("Attempting to auth user: " + username); - final String authenticatedUsersAccessToken = api.auth(username, password); - if (authenticatedUsersAccessToken != null) { - Member authenticatedMember = api.verify(authenticatedUsersAccessToken); - if (authenticatedMember != null) { - log.info("Auth successful for user: " + username); - loggedInUserService.setSignedIn(authenticatedUsersAccessToken); - return new ModelAndView(new RedirectView(urlBuilder.getBaseUrl())); - } - } - - return renderLoginScreen(true, username); - } - - @RequestMapping(value = "/logout", method = {RequestMethod.GET}) - public ModelAndView logout() throws Exception { - loggedInUserService.cleanSignedIn(); - return new ModelAndView(new RedirectView(urlBuilder.loginUrl())); - } - - private ModelAndView renderLoginScreen(boolean errors, String username) throws UnknownInstanceException { - final ModelAndView mv = new ModelAndView("login"); - final Instance instance = api.getInstance(); - mv.addObject("title", instance.getName()); - mv.addObject("instance", instance); - mv.addObject("username", username); - mv.addObject("errors", errors); - return mv; - } - -} diff --git a/src/main/java/uk/co/squadlist/web/controllers/MembersController.java b/src/main/java/uk/co/squadlist/web/controllers/MembersController.java deleted file mode 100644 index a3d2a338..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/MembersController.java +++ /dev/null @@ -1,455 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import org.apache.log4j.Logger; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.IllegalFieldValueException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.multipart.MultipartHttpServletRequest; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; -import uk.co.squadlist.web.annotations.RequiresMemberPermission; -import uk.co.squadlist.web.annotations.RequiresPermission; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.api.SquadlistApi; -import uk.co.squadlist.web.api.SquadlistApiFactory; -import uk.co.squadlist.web.auth.LoggedInUserService; -import uk.co.squadlist.web.context.GoverningBodyFactory; -import uk.co.squadlist.web.exceptions.*; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.model.Squad; -import uk.co.squadlist.web.model.forms.ChangePassword; -import uk.co.squadlist.web.model.forms.MemberDetails; -import uk.co.squadlist.web.model.forms.MemberSquad; -import uk.co.squadlist.web.services.PasswordGenerator; -import uk.co.squadlist.web.services.Permission; -import uk.co.squadlist.web.services.PermissionsService; -import uk.co.squadlist.web.urls.UrlBuilder; -import uk.co.squadlist.web.views.ViewFactory; - -import javax.validation.Valid; -import java.io.IOException; -import java.util.Date; -import java.util.List; - -@Controller -public class MembersController { - - private final static Logger log = Logger.getLogger(MembersController.class); - - private static final List GENDER_OPTIONS = Lists.newArrayList("female", "male"); - private static final List ROLES_OPTIONS = Lists.newArrayList("Rower", "Rep", "Coach", "Cox", "Non rowing"); - private static final List SWEEP_OAR_SIDE_OPTIONS = Lists.newArrayList("Bow", "Stroke", "Bow/Stroke", "Stroke/Bow"); - private static final List YES_NO_OPTIONS = Lists.newArrayList("Y", "N"); - - private InstanceSpecificApiClient instanceSpecificApiClient; - private LoggedInUserService loggedInUserService; - private UrlBuilder urlBuilder; - private ViewFactory viewFactory; - private PasswordGenerator passwordGenerator; - private PermissionsService permissionsService; - private GoverningBodyFactory governingBodyFactory; - private SquadlistApiFactory squadlistApiFactory; - private SquadlistApi squadlistApi; - - public MembersController() { - } - - @Autowired - public MembersController(InstanceSpecificApiClient instanceSpecificApiClient, LoggedInUserService loggedInUserService, UrlBuilder urlBuilder, - ViewFactory viewFactory, - PasswordGenerator passwordGenerator, - PermissionsService permissionsService, - GoverningBodyFactory governingBodyFactory, - SquadlistApiFactory squadlistApiFactory) { - this.instanceSpecificApiClient = instanceSpecificApiClient; - this.loggedInUserService = loggedInUserService; - this.urlBuilder = urlBuilder; - this.viewFactory = viewFactory; - this.passwordGenerator = passwordGenerator; - this.permissionsService = permissionsService; - this.governingBodyFactory = governingBodyFactory; - this.squadlistApiFactory = squadlistApiFactory; - this.squadlistApi = squadlistApiFactory.createClient(); - } - - @RequiresMemberPermission(permission = Permission.VIEW_MEMBER_DETAILS) - @RequestMapping("/member/{id}") - public ModelAndView member(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Member members = loggedInUserApi.getMember(id); - - final ModelAndView mv = viewFactory.getViewForLoggedInUser("memberDetails"); - mv.addObject("member", members); - mv.addObject("title", members.getFirstName() + " " + members.getLastName()); - mv.addObject("governingBody", governingBodyFactory.getGoverningBody()); - return mv; - } - - @RequiresPermission(permission = Permission.ADD_MEMBER) - @RequestMapping(value = "/member/new", method = RequestMethod.GET) - public ModelAndView newMember(@ModelAttribute("memberDetails") MemberDetails memberDetails) throws Exception { - return renderNewMemberForm(); - } - - @RequiresPermission(permission = Permission.ADD_MEMBER) - @RequestMapping(value = "/member/new", method = RequestMethod.POST) - public ModelAndView newMemberSubmit(@Valid @ModelAttribute("memberDetails") MemberDetails memberDetails, BindingResult result) throws Exception { - final List requestedSquads = extractAndValidateRequestedSquads(memberDetails, result); - - if (result.hasErrors()) { - log.info("New member submission has errors: " + result.getAllErrors()); - return renderNewMemberForm(); - } - - final String initialPassword = passwordGenerator.generateRandomPassword(10); - - try { - final Member newMember = instanceSpecificApiClient.createMember(memberDetails.getFirstName(), - memberDetails.getLastName(), - requestedSquads, - memberDetails.getEmailAddress(), - initialPassword, - null, - memberDetails.getRole() - ); - - return viewFactory.getViewForLoggedInUser("memberAdded"). - addObject("member", newMember). - addObject("initialPassword", initialPassword); - - } catch (InvalidMemberException e) { - log.warn("Invalid member exception: " + e.getMessage()); - result.addError(new ObjectError("memberDetails", e.getMessage())); - return renderNewMemberForm(); - } - } - - @RequestMapping(value = "/change-password", method = RequestMethod.GET) - public ModelAndView changePassword() throws Exception { - return renderChangePasswordForm(new ChangePassword()); - } - - @RequestMapping(value = "/change-password", method = RequestMethod.POST) - public ModelAndView editMember(@Valid @ModelAttribute("changePassword") ChangePassword changePassword, BindingResult result) throws Exception { - if (result.hasErrors()) { - return renderChangePasswordForm(changePassword); - } - - final Member member = loggedInUserService.getLoggedInMember(); - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - log.info("Requesting change password for member: " + member.getId()); - if (loggedInUserApi.changePassword(member.getId(), changePassword.getCurrentPassword(), changePassword.getNewPassword())) { - return new ModelAndView(new RedirectView(urlBuilder.memberUrl(member))); - } else { - result.addError(new ObjectError("changePassword", "Change password failed")); - return renderChangePasswordForm(changePassword); - } - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/edit", method = RequestMethod.GET) - public ModelAndView updateMember(@PathVariable String id, @RequestParam(required = false) Boolean invalidImage) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Member member = loggedInUserApi.getMember(id); - - final MemberDetails memberDetails = new MemberDetails(); - memberDetails.setFirstName(member.getFirstName()); - memberDetails.setLastName(member.getLastName()); - memberDetails.setKnownAs(member.getKnownAs()); - memberDetails.setGender(member.getGender()); - - memberDetails.setDateOfBirthDay(member.getDateOfBirth() != null ? new DateTime(member.getDateOfBirth()).getDayOfMonth() : null); - memberDetails.setDateOfBirthMonth(member.getDateOfBirth() != null ? new DateTime(member.getDateOfBirth()).getMonthOfYear() : null); - memberDetails.setDateOfBirthYear(member.getDateOfBirth() != null ? new DateTime(member.getDateOfBirth()).getYear() : null); - - memberDetails.setWeight(member.getWeight() != null ? Integer.toString(member.getWeight()) : null); - memberDetails.setEmailAddress(member.getEmailAddress()); - memberDetails.setContactNumber(member.getContactNumber()); - memberDetails.setRegistrationNumber(member.getRegistrationNumber()); - memberDetails.setRowingPoints(member.getRowingPoints()); - memberDetails.setSculling(member.getSculling()); - memberDetails.setScullingPoints(member.getScullingPoints()); - memberDetails.setSweepOarSide(member.getSweepOarSide()); - - memberDetails.setPostcode(member.getAddress() != null ? member.getAddress().get("postcode") : null); - - List memberSquads = Lists.newArrayList(); - for (Squad squad : member.getSquads()) { - memberSquads.add(new MemberSquad(squad.getId())); - } - - memberDetails.setSquads(memberSquads); - memberDetails.setEmergencyContactName(member.getEmergencyContactName()); - memberDetails.setEmergencyContactNumber(member.getEmergencyContactNumber()); - memberDetails.setRole(member.getRole()); - memberDetails.setProfileImage(member.getProfileImage()); - - ModelAndView mv = renderEditMemberDetailsForm(memberDetails, member.getId(), member.getFirstName() + " " + member.getLastName(), member); - mv.addObject("invalidImage", invalidImage); - return mv; - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/edit", method = RequestMethod.POST) - public ModelAndView updateMemberSubmit(@PathVariable String id, @Valid @ModelAttribute("member") MemberDetails memberDetails, BindingResult result) throws Exception { - log.info("Received edit member request: " + memberDetails); - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Member member = loggedInUserApi.getMember(id); - - final List squads = extractAndValidateRequestedSquads(memberDetails, result); - if (!Strings.isNullOrEmpty(memberDetails.getScullingPoints())) { - if (!governingBodyFactory.getGoverningBody().getPointsOptions().contains(memberDetails.getScullingPoints())) { - result.addError(new ObjectError("member.scullingPoints", "Invalid points option")); - } - } - if (!Strings.isNullOrEmpty(memberDetails.getRowingPoints())) { - if (!governingBodyFactory.getGoverningBody().getPointsOptions().contains(memberDetails.getRowingPoints())) { - result.addError(new ObjectError("member.rowingPoints", "Invalid points option")); - } - } - - Date updatedDateOfBirth = null; - if (memberDetails.getDateOfBirthDay() != null && - memberDetails.getDateOfBirthMonth() != null && - memberDetails.getDateOfBirthYear() != null) { - try { - updatedDateOfBirth = new DateTime(memberDetails.getDateOfBirthYear(), - memberDetails.getDateOfBirthMonth(), memberDetails.getDateOfBirthDay(), 0, 0, 0, - DateTimeZone.UTC).toDate(); - member.setDateOfBirth(updatedDateOfBirth); - } catch (IllegalFieldValueException e) { - result.addError(new ObjectError("member.dateOfBirthYear", "Invalid date")); - } - } - - if (result.hasErrors()) { - return renderEditMemberDetailsForm(memberDetails, member.getId(), member.getFirstName() + " " + member.getLastName(), member); - } - - log.info("Updating member details: " + member.getId()); - member.setFirstName(memberDetails.getFirstName()); - member.setLastName(memberDetails.getLastName()); - member.setKnownAs(memberDetails.getKnownAs()); - member.setGender(memberDetails.getGender()); - - member.setDateOfBirth(updatedDateOfBirth); - - try { - member.setWeight(!Strings.isNullOrEmpty(memberDetails.getWeight()) ? Integer.parseInt(memberDetails.getWeight()) : null); // TODO validate - } catch (IllegalArgumentException iae) { - log.warn(iae); - } - - member.setEmailAddress(memberDetails.getEmailAddress()); - member.setContactNumber(memberDetails.getContactNumber()); - member.setRowingPoints(!Strings.isNullOrEmpty(memberDetails.getRowingPoints()) ? memberDetails.getRowingPoints() : null); - member.setSculling(memberDetails.getSculling()); - member.setScullingPoints(!Strings.isNullOrEmpty(memberDetails.getScullingPoints()) ? memberDetails.getScullingPoints() : null); - member.setRegistrationNumber(memberDetails.getRegistrationNumber()); - member.setEmergencyContactName(memberDetails.getEmergencyContactName()); - member.setEmergencyContactNumber(memberDetails.getEmergencyContactNumber()); - member.setSweepOarSide(memberDetails.getSweepOarSide()); - - final boolean canChangeRole = permissionsService.canChangeRoleFor(loggedInUserService.getLoggedInMember(), member); - if (canChangeRole) { - member.setRole(memberDetails.getRole()); - } - - final boolean canChangeSquads = canChangeRole; - if (canChangeSquads) { - member.setSquads(squads); - } - - member.getAddress().put("postcode", memberDetails.getPostcode()); - - log.info("Submitting updated member: " + member); - loggedInUserApi.updateMemberDetails(member); - return new ModelAndView(new RedirectView(urlBuilder.memberUrl(member))); - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/make-inactive", method = RequestMethod.GET) - public ModelAndView makeInactivePrompt(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Member member = loggedInUserApi.getMember(id); - return viewFactory.getViewForLoggedInUser("makeMemberInactivePrompt"). - addObject("member", member). - addObject("title", "Make member inactive - " + member.getDisplayName()); - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/make-inactive", method = RequestMethod.POST) - public ModelAndView makeInactive(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - log.info("Making member inactive: " + id); - final Member member = loggedInUserApi.getMember(id); - member.setInactive(true); - loggedInUserApi.updateMemberDetails(member); - - return redirectToAdminScreen(); - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/delete", method = RequestMethod.GET) - public ModelAndView deletePrompt(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Member member = loggedInUserApi.getMember(id); - return viewFactory.getViewForLoggedInUser("deleteMemberPrompt"). - addObject("member", member). - addObject("title", "Delete member - " + member.getDisplayName()); - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/delete", method = RequestMethod.POST) - public ModelAndView delete(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Member member = loggedInUserApi.getMember(id); - - loggedInUserApi.deleteMember(member); - return redirectToAdminScreen(); - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/make-active", method = RequestMethod.GET) - public ModelAndView makeActivePrompt(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Member member = loggedInUserApi.getMember(id); - - return viewFactory.getViewForLoggedInUser("makeMemberActivePrompt"). - addObject("member", member). - addObject("title", "Make member active - " + member.getDisplayName()); - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/make-active", method = RequestMethod.POST) - public ModelAndView makeActive(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - log.info("Making member active: " + id); - final Member member = loggedInUserApi.getMember(id); - member.setInactive(false); - loggedInUserApi.updateMemberDetails(member); - return redirectToAdminScreen(); - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/edit/profileimage", method = RequestMethod.POST) - public ModelAndView updateMemberProfileImageSubmit(@PathVariable String id, MultipartHttpServletRequest request) throws UnknownMemberException, IOException { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - log.info("Received update member profile image request: " + id); - final Member member = loggedInUserApi.getMember(id); - - final MultipartFile file = request.getFile("image"); - - log.info("Submitting updated member: " + member); - try { - loggedInUserApi.updateMemberProfileImage(member, file.getBytes()); - } catch (InvalidImageException e) { - log.warn("Invalid image file submitted"); - return new ModelAndView(new RedirectView(urlBuilder.editMemberUrl(member) + "?invalidImage=true")); - } - return new ModelAndView(new RedirectView(urlBuilder.memberUrl(member))); - } - - private ModelAndView renderNewMemberForm() throws SignedInMemberRequiredException { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("newMember"); - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - mv.addObject("title", "Adding a new member"); - mv.addObject("rolesOptions", ROLES_OPTIONS); - return mv; - } - - private ModelAndView renderEditMemberDetailsForm(MemberDetails memberDetails, String memberId, String title, Member member) throws UnknownMemberException, SignedInMemberRequiredException { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("editMemberDetails"); - mv.addObject("member", memberDetails); - mv.addObject("memberId", memberId); - mv.addObject("title", title); - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - mv.addObject("governingBody", governingBodyFactory.getGoverningBody()); - - mv.addObject("genderOptions", GENDER_OPTIONS); - mv.addObject("pointsOptions", governingBodyFactory.getGoverningBody().getPointsOptions()); - mv.addObject("rolesOptions", ROLES_OPTIONS); - mv.addObject("sweepOarSideOptions", SWEEP_OAR_SIDE_OPTIONS); - mv.addObject("yesNoOptions", YES_NO_OPTIONS); - - final boolean canChangeRole = permissionsService.canChangeRoleFor(loggedInUserService.getLoggedInMember(), member); - mv.addObject("canChangeRole", canChangeRole); - mv.addObject("canChangeSquads", canChangeRole); - - mv.addObject("memberSquads", member.getSquads()); // TODO would not be needed id member.squads would form bind - return mv; - } - - private ModelAndView renderChangePasswordForm(ChangePassword changePassword) throws UnknownMemberException, SignedInMemberRequiredException { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("changePassword"); - mv.addObject("member", loggedInUserService.getLoggedInMember()); - mv.addObject("changePassword", changePassword); - mv.addObject("title", "Change password"); - return mv; - } - - private List extractAndValidateRequestedSquads(MemberDetails memberDetails, BindingResult result) { - final List squads = Lists.newArrayList(); - if (memberDetails.getSquads() == null) { - return squads; - } - - for (MemberSquad requestedSquad : memberDetails.getSquads()) { - log.info("Requested squad: " + requestedSquad); - try { - squads.add(squadlistApi.getSquad(requestedSquad.getId())); // TODO Validate instance - } catch (UnknownSquadException e) { - log.warn("Rejecting unknown squad: " + requestedSquad); - result.addError(new ObjectError("memberDetails.squad", "Unknown squad")); - } - } - log.info("Assigned squads: " + squads); - return squads; - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/reset", method = RequestMethod.GET) - public ModelAndView resetMemberPasswordPrompt(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - final Member member = loggedInUserApi.getMember(id); - return viewFactory.getViewForLoggedInUser("memberPasswordResetPrompt").addObject("member", member); - } - - @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) - @RequestMapping(value = "/member/{id}/reset", method = RequestMethod.POST) - public ModelAndView resetMemberPassword(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Member member = loggedInUserApi.getMember(id); - - final String newPassword = instanceSpecificApiClient.resetMemberPassword(member); - - return viewFactory.getViewForLoggedInUser("memberPasswordReset"). - addObject("member", member). - addObject("password", newPassword); - } - - private ModelAndView redirectToAdminScreen() { - return new ModelAndView(new RedirectView(urlBuilder.adminUrl())); - } - -} \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/controllers/MyOutingsController.java b/src/main/java/uk/co/squadlist/web/controllers/MyOutingsController.java deleted file mode 100644 index 49faea4e..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/MyOutingsController.java +++ /dev/null @@ -1,147 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import java.io.PrintWriter; -import java.util.Date; -import java.util.List; - -import javax.servlet.http.HttpServletResponse; - -import net.fortuna.ical4j.model.Calendar; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; - -import uk.co.eelpieconsulting.common.views.EtagGenerator; -import uk.co.squadlist.web.annotations.RequiresSignedInMember; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.api.SquadlistApi; -import uk.co.squadlist.web.api.SquadlistApiFactory; -import uk.co.squadlist.web.auth.LoggedInUserService; -import uk.co.squadlist.web.exceptions.UnknownMemberException; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.model.OutingAvailability; -import uk.co.squadlist.web.services.OutingAvailabilityCountsService; -import uk.co.squadlist.web.services.ical.OutingCalendarService; -import uk.co.squadlist.web.urls.UrlBuilder; -import uk.co.squadlist.web.views.DateHelper; -import uk.co.squadlist.web.views.RssOuting; -import uk.co.squadlist.web.views.SquadNamesHelper; -import uk.co.squadlist.web.views.ViewFactory; - -import com.google.common.base.Strings; -import com.google.common.collect.Lists; - -@Controller -public class MyOutingsController { - - private final LoggedInUserService loggedInUserService; - private final InstanceSpecificApiClient instanceSpecificApiClient; - private final ViewFactory viewFactory; - private final OutingAvailabilityCountsService outingAvailabilityCountsService; - private final UrlBuilder urlBuilder; - private final SquadNamesHelper squadNamesHelper; - private final OutingCalendarService outingCalendarService; - private final SquadlistApiFactory squadlistApiFactory; - private final SquadlistApi squadlistApi; - - @Autowired - public MyOutingsController(LoggedInUserService loggedInUserService, InstanceSpecificApiClient instanceSpecificApiClient, ViewFactory viewFactory, - OutingAvailabilityCountsService outingAvailabilityCountsService, UrlBuilder urlBuilder, - SquadNamesHelper squadNamesHelper, - OutingCalendarService outingCalendarService, SquadlistApiFactory squadlistApiFactory) { - this.loggedInUserService = loggedInUserService; - this.instanceSpecificApiClient = instanceSpecificApiClient; - this.viewFactory = viewFactory; - this.outingAvailabilityCountsService = outingAvailabilityCountsService; - this.urlBuilder = urlBuilder; - this.squadNamesHelper = squadNamesHelper; - this.outingCalendarService = outingCalendarService; - this.squadlistApiFactory = squadlistApiFactory; - this.squadlistApi = squadlistApiFactory.createClient(); - } - - @RequiresSignedInMember - @RequestMapping("/") - public ModelAndView outings() throws Exception { - final String loggedInUser = loggedInUserService.getLoggedInMember().getId(); - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final ModelAndView mv = viewFactory.getViewForLoggedInUser("myOutings"); - mv.addObject("member", loggedInUserApi.getMember(loggedInUser)); - - final Date startDate = DateHelper.startOfCurrentOutingPeriod().toDate(); - final Date endDate = DateHelper.oneYearFromNow().toDate(); - - mv.addObject("outings", loggedInUserApi.getAvailabilityFor(loggedInUser, startDate, endDate)); - - mv.addObject("title", "My outings"); - mv.addObject("availabilityOptions", instanceSpecificApiClient.getAvailabilityOptions()); - mv.addObject("rssUrl", urlBuilder.outingsRss(loggedInUser, instanceSpecificApiClient.getInstance())); - mv.addObject("icalUrl", urlBuilder.outingsIcal(loggedInUser, instanceSpecificApiClient.getInstance())); - return mv; - } - - @RequestMapping("/ical") - public void outingsIcal(@RequestParam(value="user", required=false) String user, HttpServletResponse response) throws Exception { - if (Strings.isNullOrEmpty(user)) { - throw new UnknownMemberException(); - } - - final Member member = squadlistApi.getMember(user); - - final Calendar calendar = outingCalendarService.buildCalendarFor(squadlistApi.getAvailabilityFor(member.getId(), - DateHelper.startOfCurrentOutingPeriod().toDate(), - DateHelper.oneYearFromNow().toDate()), - instanceSpecificApiClient.getInstance()); - - response.setStatus(HttpServletResponse.SC_OK); - response.setContentType("text/calendar"); - - final PrintWriter writer = response.getWriter(); - writer.println(calendar.toString()); - writer.flush(); - } - - @RequestMapping("/rss") - public ModelAndView outingsRss(@RequestParam(value="user", required=false) String user) throws Exception { - if (Strings.isNullOrEmpty(user)) { - throw new UnknownMemberException(); - } - - final Member member = squadlistApi.getMember(user); - - final List availabilityFor = squadlistApi.getAvailabilityFor(member.getId(), - DateHelper.startOfCurrentOutingPeriod().toDate(), - DateHelper.oneYearFromNow().toDate()); - - final String title = instanceSpecificApiClient.getInstance().getName() + " outings"; - final ModelAndView mv = new ModelAndView(new uk.co.eelpieconsulting.common.views.ViewFactory(new EtagGenerator()).getRssView(title, urlBuilder.getBaseUrl(), - squadNamesHelper.list(member.getSquads()) + " outings")); - mv.addObject("data", buildRssItemsFor(availabilityFor)); - return mv; - } - - @RequiresSignedInMember - @RequestMapping("/myoutings/ajax") - public ModelAndView ajax() throws Exception { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("myOutingsAjax"); - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - int pendingOutingsCountFor = outingAvailabilityCountsService.getPendingOutingsCountFor(loggedInUserService.getLoggedInMember().getId(), loggedInUserApi); - if (pendingOutingsCountFor > 0) { - mv.addObject("pendingOutingsCount", pendingOutingsCountFor); - } - return mv; - } - - private List buildRssItemsFor(final List availabilityFor) { - List outings = Lists.newArrayList(); - for (OutingAvailability outingAvailability : availabilityFor) { - outings.add(new RssOuting(outingAvailability.getOuting(), urlBuilder.outingUrl(outingAvailability.getOuting()))); - } - return outings; - } - -} diff --git a/src/main/java/uk/co/squadlist/web/controllers/OutingsController.java b/src/main/java/uk/co/squadlist/web/controllers/OutingsController.java deleted file mode 100644 index 8056eff3..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/OutingsController.java +++ /dev/null @@ -1,363 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletResponse; -import javax.validation.Valid; - -import org.apache.log4j.Logger; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.map.JsonMappingException; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.LocalDateTime; -import org.joda.time.format.ISODateTimeFormat; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; - -import uk.co.eelpieconsulting.common.http.HttpFetchException; -import uk.co.squadlist.web.annotations.RequiresOutingPermission; -import uk.co.squadlist.web.annotations.RequiresPermission; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.api.SquadlistApi; -import uk.co.squadlist.web.api.SquadlistApiFactory; -import uk.co.squadlist.web.auth.LoggedInUserService; -import uk.co.squadlist.web.exceptions.*; -import uk.co.squadlist.web.model.AvailabilityOption; -import uk.co.squadlist.web.model.Instance; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.model.Outing; -import uk.co.squadlist.web.model.OutingAvailability; -import uk.co.squadlist.web.model.OutingWithSquadAvailability; -import uk.co.squadlist.web.model.Squad; -import uk.co.squadlist.web.model.forms.OutingDetails; -import uk.co.squadlist.web.services.OutingAvailabilityCountsService; -import uk.co.squadlist.web.services.Permission; -import uk.co.squadlist.web.services.PreferedSquadService; -import uk.co.squadlist.web.services.filters.ActiveMemberFilter; -import uk.co.squadlist.web.urls.UrlBuilder; -import uk.co.squadlist.web.views.CsvOutputRenderer; -import uk.co.squadlist.web.views.DateFormatter; -import uk.co.squadlist.web.views.DateHelper; -import uk.co.squadlist.web.views.ViewFactory; - -import com.google.common.base.Strings; -import com.google.common.collect.Lists; - -@Controller -public class OutingsController { - - private final static Logger log = Logger.getLogger(OutingsController.class); - - private LoggedInUserService loggedInUserService; - private InstanceSpecificApiClient instanceSpecificApiClient; - private UrlBuilder urlBuilder; - private DateFormatter dateFormatter; - private PreferedSquadService preferedSquadService; - private ViewFactory viewFactory; - private OutingAvailabilityCountsService outingAvailabilityCountsService; - private ActiveMemberFilter activeMemberFilter; - private CsvOutputRenderer csvOutputRenderer; - private SquadlistApiFactory squadlistApiFactory; - private SquadlistApi squadlistApi; - - public OutingsController() { - } - - @Autowired - public OutingsController(LoggedInUserService loggedInUserService, InstanceSpecificApiClient instanceSpecificApiClient, UrlBuilder urlBuilder, - DateFormatter dateFormatter, PreferedSquadService preferedSquadService, ViewFactory viewFactory, - OutingAvailabilityCountsService outingAvailabilityCountsService, ActiveMemberFilter activeMemberFilter, - CsvOutputRenderer csvOutputRenderer, SquadlistApiFactory squadlistApiFactory) { - this.loggedInUserService = loggedInUserService; - this.instanceSpecificApiClient = instanceSpecificApiClient; - this.urlBuilder = urlBuilder; - this.dateFormatter = dateFormatter; - this.preferedSquadService = preferedSquadService; - this.viewFactory = viewFactory; - this.outingAvailabilityCountsService = outingAvailabilityCountsService; - this.activeMemberFilter = activeMemberFilter; - this.csvOutputRenderer = csvOutputRenderer; - this.squadlistApiFactory = squadlistApiFactory; - this.squadlistApi = squadlistApiFactory.createClient(); - } - - @RequestMapping("/outings") - public ModelAndView outings(@RequestParam(required = false, value = "squad") String squadId, - @RequestParam(value = "month", required = false) String month) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Squad squadToShow = preferedSquadService.resolveSquad(squadId); - final ModelAndView mv = viewFactory.getViewForLoggedInUser("outings"); - if (squadToShow == null) { - mv.addObject("title", "Outings"); - return mv; - } - - Date startDate = DateHelper.startOfCurrentOutingPeriod().toDate(); - Date endDate = DateHelper.endOfCurrentOutingPeriod().toDate(); - - String title = squadToShow.getName() + " outings"; - if (month != null) { - final DateTime monthDateTime = ISODateTimeFormat.yearMonth().parseDateTime(month); // TODO Can be moved to spring? - startDate = monthDateTime.toDate(); - endDate = monthDateTime.plusMonths(1).toDate(); - title = squadToShow.getName() + " outings - " + dateFormatter.fullMonthYear(startDate); - } else { - mv.addObject("current", true); - } - - mv.addObject("title", title); - mv.addObject("squad", squadToShow); - mv.addObject("startDate", startDate); - mv.addObject("endDate", endDate); - mv.addObject("month", month); - mv.addObject("outingMonths", getOutingMonthsFor(squadToShow)); - - final List squadOutings = squadlistApi.getSquadAvailability(squadToShow.getId(), startDate, endDate); - mv.addObject("outings", squadOutings); - mv.addObject("outingAvailabilityCounts", outingAvailabilityCountsService.buildOutingAvailabilityCounts(squadOutings)); - - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - return mv; - } - - @RequestMapping("/outings/{id}") - public ModelAndView outing(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Outing outing = loggedInUserApi.getOuting(id); - - final ModelAndView mv = viewFactory.getViewForLoggedInUser("outing"); - mv.addObject("title", outing.getSquad().getName() + " - " + dateFormatter.dayMonthYearTime(outing.getDate())); - mv.addObject("outing", outing); - mv.addObject("outingMonths", getOutingMonthsFor(outing.getSquad())); - mv.addObject("squad", outing.getSquad()); - mv.addObject("squadAvailability", loggedInUserApi.getOutingAvailability(outing.getId())); - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - mv.addObject("members", activeMemberFilter.extractActive(squadlistApi.getSquadMembers(outing.getSquad().getId()))); - mv.addObject("month", ISODateTimeFormat.yearMonth().print(outing.getDate().getTime())); // TODO push to date parser - local time - return mv; - } - - @RequestMapping("/outings/{id}.csv") - public void outingCsv(@PathVariable String id, HttpServletResponse response) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Outing outing = loggedInUserApi.getOuting(id); - final List squadMembers = squadlistApi.getSquadMembers(outing.getSquad().getId()); - final Map outingAvailability = loggedInUserApi.getOutingAvailability(outing.getId()); - - final List> rows = Lists.newArrayList(); - for (Member member : squadMembers) { - rows.add(Arrays.asList( - dateFormatter.dayMonthYearTime(outing.getDate()), - outing.getSquad().getName(), - member.getDisplayName(), - member.getRole(), - outingAvailability.get(member.getId()) != null ? outingAvailability.get(member.getId()).getLabel() : null - )); - } - csvOutputRenderer.renderCsvResponse(response, Lists.newArrayList("Date", "Squad", "Member", "Role", "Availability"), rows); - } - - @RequiresPermission(permission = Permission.ADD_OUTING) - @RequestMapping(value = "/outings/new", method = RequestMethod.GET) // TODO fails hard if no squads are available - public ModelAndView newOuting() throws Exception { - final LocalDateTime defaultOutingDateTime = DateHelper.defaultOutingStartDateTime(); - final OutingDetails outingDefaults = new OutingDetails(defaultOutingDateTime); - outingDefaults.setSquad(preferedSquadService.resolveSquad(null).getId()); - return renderNewOutingForm(outingDefaults); - } - - @RequiresPermission(permission = Permission.ADD_OUTING) - @RequestMapping(value = "/outings/new", method = RequestMethod.POST) - public ModelAndView newOutingSubmit(@Valid @ModelAttribute("outing") OutingDetails outingDetails, BindingResult result) throws Exception { - if (result.hasErrors()) { - return renderNewOutingForm(outingDetails); - } - - try { - final Outing newOuting = buildOutingFromOutingDetails(outingDetails, instanceSpecificApiClient.getInstance()); - if (outingDetails.getRepeats() != null && outingDetails.getRepeats() && outingDetails.getRepeatsCount() != null) { - squadlistApi.createOuting(newOuting, outingDetails.getRepeatsCount()); - } else { - squadlistApi.createOuting(newOuting, null); - } - - final String outingsViewForNewOutingsSquadAndMonth = urlBuilder.outings(newOuting.getSquad(), new DateTime(newOuting.getDate()).toString("yyyy-MM")); - return new ModelAndView(new RedirectView(outingsViewForNewOutingsSquadAndMonth)); - - } catch (InvalidOutingException e) { - result.addError(new ObjectError("outing", e.getMessage())); - return renderNewOutingForm(outingDetails); - - } catch (Exception e) { - result.addError(new ObjectError("outing", "Unknown error")); - return renderNewOutingForm(outingDetails); - } - } - - @RequiresOutingPermission(permission = Permission.EDIT_OUTING) - @RequestMapping(value = "/outings/{id}/edit", method = RequestMethod.GET) - public ModelAndView outingEdit(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Outing outing = loggedInUserApi.getOuting(id); - - final OutingDetails outingDetails = new OutingDetails(new LocalDateTime(outing.getDate())); - outingDetails.setSquad(outing.getSquad().getId()); - outingDetails.setNotes(outing.getNotes()); - - return renderEditOutingForm(outingDetails, outing); - } - - @RequiresOutingPermission(permission = Permission.EDIT_OUTING) - @RequestMapping(value = "/outings/{id}/delete", method = RequestMethod.GET) - public ModelAndView deleteOutingPrompt(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - final Outing outing = loggedInUserApi.getOuting(id); - return viewFactory.getViewForLoggedInUser("deleteOuting").addObject("outing", outing); - } - - @RequiresOutingPermission(permission = Permission.EDIT_OUTING) - @RequestMapping(value = "/outings/{id}/delete", method = RequestMethod.POST) - public ModelAndView deleteOuting(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - final Outing outing = loggedInUserApi.getOuting(id); - - squadlistApi.deleteOuting(outing.getId()); - - final String exitUrl = outing.getSquad() == null ? urlBuilder.outings(outing.getSquad()) : urlBuilder.outingsUrl(); - return new ModelAndView(new RedirectView(exitUrl)); - } - - @RequiresOutingPermission(permission = Permission.EDIT_OUTING) - @RequestMapping(value = "/outings/{id}/close", method = RequestMethod.GET) - public ModelAndView closeOuting(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Outing outing = loggedInUserApi.getOuting(id); - - log.info("Closing outing: " + outing); - outing.setClosed(true); - loggedInUserApi.updateOuting(outing); - - return redirectToOuting(outing); - } - - @RequiresOutingPermission(permission = Permission.EDIT_OUTING) - @RequestMapping(value = "/outings/{id}/reopen", method = RequestMethod.GET) - public ModelAndView reopenOuting(@PathVariable String id) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Outing outing = loggedInUserApi.getOuting(id); - - log.info("Reopening outing: " + outing); - outing.setClosed(false); - loggedInUserApi.updateOuting(outing); - - return redirectToOuting(outing); - } - - @RequiresOutingPermission(permission = Permission.EDIT_OUTING) - @RequestMapping(value = "/outings/{id}/edit", method = RequestMethod.POST) - public ModelAndView editOutingSubmit(@PathVariable String id, - @Valid @ModelAttribute("outing") OutingDetails outingDetails, BindingResult result) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Outing outing = loggedInUserApi.getOuting(id); - if (result.hasErrors()) { - return renderEditOutingForm(outingDetails, outing); - } - try { - final Outing updatedOuting = buildOutingFromOutingDetails(outingDetails, instanceSpecificApiClient.getInstance()); - updatedOuting.setId(id); - - loggedInUserApi.updateOuting(updatedOuting); - return redirectToOuting(updatedOuting); - - } catch (InvalidOutingException e) { - result.addError(new ObjectError("outing", e.getMessage())); - return renderEditOutingForm(outingDetails, outing); - - } catch (Exception e) { - log.error(e); - result.addError(new ObjectError("outing", "Unknown exception")); - return renderEditOutingForm(outingDetails, outing); - } - } - - @RequestMapping(value = "/availability/ajax", method = RequestMethod.POST) - public ModelAndView updateAvailability( - @RequestParam(value = "outing", required = true) String outingId, - @RequestParam(value = "availability", required = true) String availability) throws Exception { - SquadlistApi loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()); - - final Outing outing = loggedInUserApi.getOuting(outingId); - - if (!outing.isClosed()) { - final OutingAvailability result = loggedInUserApi.setOutingAvailability(loggedInUserService.getLoggedInMember(), outing, getAvailabilityOptionById(availability)); - return viewFactory.getViewForLoggedInUser("includes/availability").addObject("availability", result.getAvailabilityOption()); - } - - throw new OutingClosedException(); - } - - private ModelAndView redirectToOuting(final Outing updatedOuting) { - return new ModelAndView(new RedirectView(urlBuilder.outingUrl(updatedOuting))); - } - - private ModelAndView renderNewOutingForm(OutingDetails outingDetails) throws UnknownMemberException, UnknownSquadException, UnknownInstanceException, SignedInMemberRequiredException { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("newOuting"); - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - final Squad squad = preferedSquadService.resolveSquad(null); - mv.addObject("squad", squad); - mv.addObject("outingMonths", getOutingMonthsFor(squad)); - mv.addObject("outing", outingDetails); - return mv; - } - - private ModelAndView renderEditOutingForm(OutingDetails outingDetails, Outing outing) throws UnknownMemberException, UnknownSquadException, UnknownInstanceException, SignedInMemberRequiredException { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("editOuting"); - mv.addObject("squads", instanceSpecificApiClient.getSquads()); - mv.addObject("squad", outing.getSquad()); - mv.addObject("outing", outingDetails); - mv.addObject("outingObject", outing); - mv.addObject("outingMonths", getOutingMonthsFor(outing.getSquad())); - mv.addObject("month", ISODateTimeFormat.yearMonth().print(outing.getDate().getTime())); // TODO push to date parser - local time - return mv; - } - - private AvailabilityOption getAvailabilityOptionById(String availabilityId) throws JsonParseException, JsonMappingException, HttpFetchException, IOException, UnknownAvailabilityOptionException { - if (Strings.isNullOrEmpty(availabilityId)) { - return null; - } - return instanceSpecificApiClient.getAvailabilityOption(availabilityId); - } - - private Map getOutingMonthsFor(final Squad squad) { - return instanceSpecificApiClient.getOutingMonths(squad); - } - - private Outing buildOutingFromOutingDetails(OutingDetails outingDetails, Instance instance) throws UnknownSquadException { - Date date = outingDetails.toLocalTime().toDateTime(DateTimeZone.forID(instance.getTimeZone())).toDate(); - Squad squad = outingDetails.getSquad() != null ? squadlistApi.getSquad(outingDetails.getSquad()) : null; // TODO validation - String notes = outingDetails.getNotes(); - return new Outing(squad, date, notes); - } - -} diff --git a/src/main/java/uk/co/squadlist/web/controllers/ResetPasswordController.java b/src/main/java/uk/co/squadlist/web/controllers/ResetPasswordController.java deleted file mode 100644 index a7d8aad6..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/ResetPasswordController.java +++ /dev/null @@ -1,74 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import org.apache.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; - -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.exceptions.UnknownMemberException; - -import com.google.common.base.Strings; - -@Controller -public class ResetPasswordController { - - private final static Logger log = Logger.getLogger(ResetPasswordController.class); - - private final InstanceSpecificApiClient api; - - @Autowired - public ResetPasswordController(InstanceSpecificApiClient api) { - this.api = api; - } - - @RequestMapping(value = "/reset-password", method = RequestMethod.GET) - public ModelAndView resetPasswordPrompt() throws Exception { - final ModelAndView mv = new ModelAndView("resetPassword"); - mv.addObject("title", "Reset password"); - return mv; - } - - @RequestMapping(value = "/reset-password", method = RequestMethod.POST) - public ModelAndView resetPassword(@RequestParam(value = "username", required = false) String username) throws Exception { - if (Strings.isNullOrEmpty(username)) { - final ModelAndView mv = new ModelAndView("resetPassword"); - mv.addObject("errors", true); - mv.addObject("title", "Reset password"); - return mv; - } - - log.info("Reseting password for: " + username); - try { - api.resetPassword(username); // TODO errors - log.info("Reset password call successful for: " + username); - final ModelAndView mv = new ModelAndView("resetPasswordSent"); - mv.addObject("title", "Reset password"); - return mv; - - } catch (UnknownMemberException e) { - final ModelAndView mv = new ModelAndView("resetPassword"); - mv.addObject("title", "Reset password"); - mv.addObject("errors", true); - return mv; - } - } - - @RequestMapping(value = "/reset-password/confirm", method = RequestMethod.GET) - public ModelAndView confirmPasswordReset(@RequestParam String token) throws Exception { - try { - final String newPassword = api.confirmResetPassword(token); - - final ModelAndView mv = new ModelAndView("resetPasswordConfirm"); - mv.addObject("newPassword", newPassword); - return mv; - - } catch (Exception e) { - return new ModelAndView("resetPasswordInvalidToken"); - } - } - -} \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/controllers/SquadsController.java b/src/main/java/uk/co/squadlist/web/controllers/SquadsController.java deleted file mode 100644 index 00122b45..00000000 --- a/src/main/java/uk/co/squadlist/web/controllers/SquadsController.java +++ /dev/null @@ -1,158 +0,0 @@ -package uk.co.squadlist.web.controllers; - -import java.io.IOException; -import java.util.List; -import java.util.Set; - -import javax.validation.Valid; - -import org.apache.log4j.Logger; -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.map.JsonMappingException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; - -import uk.co.eelpieconsulting.common.http.HttpBadRequestException; -import uk.co.eelpieconsulting.common.http.HttpFetchException; -import uk.co.eelpieconsulting.common.http.HttpForbiddenException; -import uk.co.eelpieconsulting.common.http.HttpNotFoundException; -import uk.co.squadlist.web.annotations.RequiresPermission; -import uk.co.squadlist.web.api.InstanceSpecificApiClient; -import uk.co.squadlist.web.api.SquadlistApi; -import uk.co.squadlist.web.api.SquadlistApiFactory; -import uk.co.squadlist.web.context.InstanceConfig; -import uk.co.squadlist.web.exceptions.InvalidSquadException; -import uk.co.squadlist.web.exceptions.SignedInMemberRequiredException; -import uk.co.squadlist.web.exceptions.UnknownInstanceException; -import uk.co.squadlist.web.exceptions.UnknownSquadException; -import uk.co.squadlist.web.model.Instance; -import uk.co.squadlist.web.model.Member; -import uk.co.squadlist.web.model.Squad; -import uk.co.squadlist.web.model.forms.SquadDetails; -import uk.co.squadlist.web.services.Permission; -import uk.co.squadlist.web.urls.UrlBuilder; -import uk.co.squadlist.web.views.ViewFactory; - -import com.google.common.base.Splitter; -import com.google.common.collect.Sets; - -@Controller -public class SquadsController { - - private final static Logger log = Logger.getLogger(SquadsController.class); - - private final static Splitter COMMA_SPLITTER = Splitter.on(","); - - private InstanceSpecificApiClient instanceSpecificApiClient; - private UrlBuilder urlBuilder; - private ViewFactory viewFactory; - private SquadlistApi squadlistApi; - private InstanceConfig instanceConfig; - - public SquadsController() { - } - - @Autowired - public SquadsController(InstanceSpecificApiClient instanceSpecificApiClient, - UrlBuilder urlBuilder, ViewFactory viewFactory, SquadlistApiFactory squadlistApiFactory, - InstanceConfig instanceConfig) { - this.instanceSpecificApiClient = instanceSpecificApiClient; - this.urlBuilder = urlBuilder; - this.viewFactory = viewFactory; - this.squadlistApi = squadlistApiFactory.createClient(); - this.instanceConfig = instanceConfig; - } - - @RequestMapping(value="/squad/new", method=RequestMethod.GET) - public ModelAndView newSquad(@ModelAttribute("squadDetails") SquadDetails squadDetails) throws Exception { - return renderNewSquadForm(new SquadDetails()); - } - - @RequestMapping(value="/squad/new", method=RequestMethod.POST) - public ModelAndView newSquadSubmit(@Valid @ModelAttribute("squadDetails") SquadDetails squadDetails, BindingResult result) throws UnknownInstanceException, SignedInMemberRequiredException { - if (result.hasErrors()) { - return renderNewSquadForm(squadDetails); - } - - try { - Instance instance = squadlistApi.getInstance(instanceConfig.getInstance()); - squadlistApi.createSquad(instance, squadDetails.getName()); - - return new ModelAndView(new RedirectView(urlBuilder.adminUrl())); - - } catch (InvalidSquadException e) { - log.info("Invalid squad"); - result.rejectValue("name", null, "squad name is already in use"); - return renderNewSquadForm(squadDetails); - } - } - - @RequiresPermission(permission=Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value="/squad/{id}/delete", method=RequestMethod.GET) - public ModelAndView deletePrompt(@PathVariable String id) throws UnknownSquadException, SignedInMemberRequiredException { - final Squad squad = squadlistApi.getSquad(id); - return viewFactory.getViewForLoggedInUser("deleteSquadPrompt").addObject("squad", squad).addObject("title", "Delete squad"); - } - - @RequiresPermission(permission=Permission.VIEW_ADMIN_SCREEN) - @RequestMapping(value="/squad/{id}/delete", method=RequestMethod.POST) - public ModelAndView delete(@PathVariable String id) throws UnknownSquadException { - final Squad squad = squadlistApi.getSquad(id); - squadlistApi.deleteSquad(squad); - return new ModelAndView(new RedirectView(urlBuilder.adminUrl())); - } - - @RequestMapping(value="/squad/{id}/edit", method=RequestMethod.GET) - public ModelAndView editSquad(@PathVariable String id) throws UnknownSquadException, SignedInMemberRequiredException { - final Squad squad = squadlistApi.getSquad(id); - - final SquadDetails squadDetails = new SquadDetails(); - squadDetails.setName(squad.getName()); - - return renderEditSquadForm(squad, squadDetails); - } - - @RequestMapping(value="/squad/{id}/edit", method=RequestMethod.POST) - public ModelAndView editSquadSubmit(@PathVariable String id, @Valid @ModelAttribute("squadDetails") SquadDetails squadDetails, BindingResult result) throws UnknownSquadException, JsonGenerationException, JsonMappingException, HttpNotFoundException, HttpBadRequestException, HttpForbiddenException, IOException, HttpFetchException, SignedInMemberRequiredException { - final Squad squad = squadlistApi.getSquad(id); - if (result.hasErrors()) { - return renderEditSquadForm(squad, squadDetails); - } - - squad.setName(squadDetails.getName()); - log.info("Updating squad: " + squad); - squadlistApi.updateSquad(squad); - - final Set updatedSquadMembers = Sets.newHashSet(COMMA_SPLITTER.split(squadDetails.getMembers()).iterator()); - log.info("Setting squad members to " + updatedSquadMembers.size() + " members: " + updatedSquadMembers); - squadlistApi.setSquadMembers(squad.getId(), updatedSquadMembers); - - return new ModelAndView(new RedirectView(urlBuilder.adminUrl())); - } - - private ModelAndView renderNewSquadForm(SquadDetails squadDetails) throws SignedInMemberRequiredException { - return viewFactory.getViewForLoggedInUser("newSquad").addObject("squadDetails", squadDetails); - } - - private ModelAndView renderEditSquadForm(final Squad squad, final SquadDetails squadDetails) throws SignedInMemberRequiredException { - final ModelAndView mv = viewFactory.getViewForLoggedInUser("editSquad"); - mv.addObject("squad", squad); - mv.addObject("squadDetails", squadDetails); - - final List squadMembers = squadlistApi.getSquadMembers(squad.getId()); - mv.addObject("squadMembers", squadMembers); - - final List availableMembers = instanceSpecificApiClient.getMembers(); - availableMembers.removeAll(squadMembers); - mv.addObject("availableMembers", availableMembers); - return mv; - } - -} diff --git a/src/main/java/uk/co/squadlist/web/model/forms/AvailabilityOptionDetails.java b/src/main/java/uk/co/squadlist/web/model/forms/AvailabilityOptionDetails.java index 53ad2508..0100dd18 100644 --- a/src/main/java/uk/co/squadlist/web/model/forms/AvailabilityOptionDetails.java +++ b/src/main/java/uk/co/squadlist/web/model/forms/AvailabilityOptionDetails.java @@ -3,30 +3,30 @@ import org.hibernate.validator.constraints.NotBlank; public class AvailabilityOptionDetails { - - @NotBlank - private String name, colour; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getColour() { - return colour; - } - - public void setColour(String colour) { - this.colour = colour; - } - - @Override - public String toString() { - return "AvailabilityOptionDetails [colour=" + colour + ", name=" + name - + "]"; - } - + + @NotBlank + private String name, colour; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getColour() { + return colour; + } + + public void setColour(String colour) { + this.colour = colour; + } + + @Override + public String toString() { + return "AvailabilityOptionDetails [colour=" + colour + ", name=" + name + + "]"; + } + } diff --git a/src/main/java/uk/co/squadlist/web/services/github/GithubService.java b/src/main/java/uk/co/squadlist/web/services/github/GithubService.java deleted file mode 100644 index 3b217733..00000000 --- a/src/main/java/uk/co/squadlist/web/services/github/GithubService.java +++ /dev/null @@ -1,77 +0,0 @@ -package uk.co.squadlist.web.services.github; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.apache.log4j.Logger; -import org.kohsuke.github.GHIssue; -import org.kohsuke.github.GHIssueState; -import org.kohsuke.github.GitHub; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import com.google.common.base.Strings; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.Lists; - -@Component -public class GithubService { - - private final static Logger log = Logger.getLogger(GithubService.class); - - private static final String GITHUB_REPO = "eelpie/squadlist"; - - private GitHub github; - - private Cache> cache; - - @Autowired - public GithubService(@Value("${github.username}") String username, @Value("${github.token}") String token) { - this.cache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(); - try { - log.info("Github config: " + username + "/" + token); - if (!Strings.isNullOrEmpty(username) && !Strings.isNullOrEmpty(token)) { - this.github = GitHub.connect(username, token); - } else { - log.warn("Github service non configured"); - } - - } catch (IOException e) { - log.error(e); - } - } - - public List getOpenIssues() { - return getIssuesWhichAre(GHIssueState.OPEN); - } - - public List getClosedIssues() { - return getIssuesWhichAre(GHIssueState.CLOSED); - } - - private List getIssuesWhichAre(final GHIssueState state) { - List cached = cache.getIfPresent(state); - if (cached != null) { - log.info("Returning cached for: " + state.toString()); - return cached; - } - - if (github == null) { - return Lists.newArrayList(); - } - try { - log.info("Fetching issues from github: " + state); - List issues = github.getRepository(GITHUB_REPO).getIssues(state); - cache.put(state, issues); - return issues; - - } catch (IOException e) { - log.error(e); - } - return Lists.newArrayList(); - } - -} \ No newline at end of file diff --git a/src/main/java/uk/co/squadlist/web/urls/SeoLinkBuilder.java b/src/main/java/uk/co/squadlist/web/urls/SeoLinkBuilder.java deleted file mode 100644 index c53e176c..00000000 --- a/src/main/java/uk/co/squadlist/web/urls/SeoLinkBuilder.java +++ /dev/null @@ -1,15 +0,0 @@ -package uk.co.squadlist.web.urls; - -import org.springframework.stereotype.Component; - -@Component -public class SeoLinkBuilder { - - public String makeSeoLinkFor(String name) { - String result = name.toLowerCase().trim().replaceAll("\\s", "-"); - result = result.replaceAll("[^\\-a-z0-9_]", ""); - result = result.replaceAll("--+", "-"); - return result; - } - -} diff --git a/src/main/java/uk/co/squadlist/web/urls/UrlBuilder.java b/src/main/java/uk/co/squadlist/web/urls/UrlBuilder.java deleted file mode 100644 index 56103d73..00000000 --- a/src/main/java/uk/co/squadlist/web/urls/UrlBuilder.java +++ /dev/null @@ -1,291 +0,0 @@ -package uk.co.squadlist.web.urls; - -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.http.client.utils.URIBuilder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import uk.co.squadlist.web.context.InstanceConfig; -import uk.co.squadlist.web.localisation.GoverningBody; -import uk.co.squadlist.web.model.*; - -import java.net.URISyntaxException; -import java.util.List; - -@Component("urlBuilder") -public class UrlBuilder { - - private final String baseUrl; - private final InstanceConfig instanceConfig; - private final SeoLinkBuilder seoLinkBuilder; - private final String apiUrl; - private final String profilePicturesUrl; - - @Autowired - public UrlBuilder(@Value("${baseUrl}") String baseUrl, InstanceConfig instanceConfig, SeoLinkBuilder seoLinkBuilder, - @Value("${apiUrl}") String apiUrl, - @Value("${profilePicturesUrl}") String profilePicturesUrl - ) { - this.baseUrl = baseUrl; - this.instanceConfig = instanceConfig; - this.seoLinkBuilder = seoLinkBuilder; - this.apiUrl = apiUrl; - this.profilePicturesUrl = profilePicturesUrl; - } - - public String applicationUrl(String uri) { - return getBaseUrl() + uri; - } - - public String loginUrl() { - return applicationUrl("/login"); - } - - public String staticUrl(String uri) { - return applicationUrl("/assets/") + uri; - } - - public String boatUrl(Boat boat) { - return applicationUrl("/boats/" + boat.getId()); - } - public String memberUrl(Member member) { - return memberUrl(member.getId()); - } - - public String memberResetPasswordUrl(Member member) { - return memberUrl(member.getId()) + "/reset"; - } - - public String memberUrl(String memberId) { - return applicationUrl("/member/" + memberId); - } - - public String makeActive(Member member) { - return memberUrl(member.getId()) + "/make-active"; - } - - public String makeInactive(Member member) { - return memberUrl(member.getId()) + "/make-inactive"; - } - - public String delete(Member member) { - return memberUrl(member.getId()) + "/delete"; - } - - public String delete(Squad squad) { - return applicationUrl("/squad/" + squad.getId() + "/delete"); - } - - public String newMemberUrl() { - return applicationUrl("/member/new"); - } - - public String newSquadUrl() { - return applicationUrl("/squad/new"); - } - - public String editSquadUrl(Squad squad) { - return applicationUrl("/squad/" + squad.getId() + "/edit"); - } - - public String adminUrl() { - return applicationUrl("/admin"); - } - - public String outingsUrl() { - return applicationUrl("/outings"); - } - - public String outingUrl(Outing outing) { - return outingsUrl() + "/" + outing.getId(); - } - - public String outingAvailabilityCsv(Outing outing) { - return outingUrl(outing) + ".csv"; - } - - public String outingCloseUrl(Outing outing) { - return outingUrl(outing) + "/close"; - } - - public String deleteOuting(Outing outing) { - return outingUrl(outing) + "/delete"; - } - - public String outingReopenUrl(Outing outing) { - return outingUrl(outing) + "/reopen"; - } - - public String outingEditUrl(Outing outing) { - return outingUrl(outing) + "/edit"; - } - - public String outings(Squad squad) { - return applicationUrl("/outings?squad=" + squad.getId()); - } - - public String outings(Squad squad, String month) { - return outings(squad) + "&month=" + month; - } - - public String availability(Squad squad) { - return applicationUrl("/availability/" + squad.getId()); - } - - public String availability(Squad squad, String month) { - return availability(squad) + "?month=" + month; - } - - public String editInstanceSettings() { - return adminUrl() + "/instance"; - } - - public String editMemberUrl(Member member) { - return memberUrl(member) + "/edit"; - } - - public String editMemberUrl(String memberId) { - return memberUrl(memberId) + "/edit"; - } - - public String entryDetailsCsv(Squad squad) { - return applicationUrl("/entrydetails/" + squad.getId() + ".csv"); - } - - public String entryDetailsCsv(List members) { - try { - final URIBuilder url = new URIBuilder(applicationUrl("/entrydetails/selected.csv")); - - final List memberIds = Lists.newArrayList(); - for (Member member : members) { - memberIds.add(member.getId()); - } - url.addParameter("members", Joiner.on(",").join(memberIds)); - - return url.toString(); - - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - public String changePassword() { - return applicationUrl("/change-password"); - } - - public String socialMediaAccounts() { - return applicationUrl("/social"); - } - - public String linkFacebookUrl() { - return applicationUrl("/social/facebook/link"); - } - - public String removeFacebookUrl() { - return applicationUrl("/social/facebook/remove"); - } - - public String getLinkFacebookCallbackUrl() { - return applicationUrl("/social/facebook/link/callback"); - } - - public String facebookSigninCallbackUrl() { - return applicationUrl("/social/facebook/signin/callback"); - } - - public String facebookSignin() { - return applicationUrl("/social/facebook/signin"); - } - - public String getBaseUrl() { - return baseUrl.replace("INSTANCE", instanceConfig.getVhost()); - } - - public String contactsUrl() { - return applicationUrl("/contacts"); - } - public String contactsUrl(Squad prefferredSquad) { - return appendSquad(prefferredSquad, contactsUrl()); - } - - public String outingsUrl(Squad prefferredSquad) throws URISyntaxException { - final URIBuilder url = new URIBuilder(outingsUrl()); - if (prefferredSquad != null) { - url.addParameter("squad", prefferredSquad.getId()); - } - return url.build().toString(); - } - - public String outingsRss(String userid, Instance instance) throws URISyntaxException { - final URIBuilder url = new URIBuilder(applicationUrl("/rss")); - url.addParameter("user", userid); - url.addParameter("key", generateFeedKeyFor(userid, instance)); - return url.build().toString(); - } - - public String outingsIcal(String userid, Instance instance) throws URISyntaxException { - String webcalBaseUrl = getBaseUrl().replaceAll("^https?://", "webcal://"); - final URIBuilder url = new URIBuilder(webcalBaseUrl + "/ical"); - url.addParameter("user", userid); - url.addParameter("key", generateFeedKeyFor(userid, instance)); - return url.build().toString(); - } - - public String availabilityUrl(Squad prefferredSquad) { - return appendSquad(prefferredSquad, availabilityUrl()); - } - - public String governingBody(GoverningBody governingBody) { - return applicationUrl("/governing-body/" + seoLinkBuilder.makeSeoLinkFor(governingBody.getName())); - } - - public String staticImage(String filename) { - return apiUrl + "/static/" + filename + ".jpg"; - } - - public String profileImage(String filename) { - return profilePicturesUrl + "/" + filename + ".jpg"; // TODO ideally all of this would be provided by the API - } - - public String newAvailabilityOptionUrl() { - return applicationUrl("/availability-option/new"); - } - - public String deleteAvailabilityOptionUrl(AvailabilityOption availabilityOption) { - return applicationUrl("/availability-option/" + availabilityOption.getId() + "/delete"); - } - - public String editAvailabilityOptionUrl(AvailabilityOption availabilityOption) { - return applicationUrl("/availability-option/" + availabilityOption.getId() + "/edit"); - } - - public String editAdmins() { - return adminUrl() + "/admins"; - } - - public String adminExportMembersAsCSV() { - return adminUrl() + "/export/members.csv"; - } - - private String availabilityUrl() { - return applicationUrl("/availability"); - } - - private String appendSquad(Squad prefferredSquad, final String baseUrl) { - return prefferredSquad != null ? baseUrl + "/" + prefferredSquad.getId() : baseUrl; - } - - private String generateFeedKeyFor(String userid, Instance instance) { - return DigestUtils.md5Hex(instance.getId() + userid); - } - - public String mailto(List emails) { - if (emails.isEmpty()) { - return null; - } - return "mailto:" + Joiner.on(",").join(emails); - } - -} diff --git a/src/main/java/uk/co/squadlist/web/views/RssOuting.java b/src/main/java/uk/co/squadlist/web/views/RssOuting.java deleted file mode 100644 index aab23877..00000000 --- a/src/main/java/uk/co/squadlist/web/views/RssOuting.java +++ /dev/null @@ -1,54 +0,0 @@ -package uk.co.squadlist.web.views; - -import java.util.Date; - -import uk.co.eelpieconsulting.common.geo.model.LatLong; -import uk.co.eelpieconsulting.common.views.rss.RssFeedable; -import uk.co.squadlist.web.model.Outing; - -public class RssOuting implements RssFeedable { - - private final Outing outing; - private final String url; - - public RssOuting(Outing outing, String url) { - this.outing = outing; - this.url = url; - } - - @Override - public String getAuthor() { - return null; - } - - @Override - public Date getDate() { - return outing.getDate(); - } - - @Override - public String getDescription() { - return outing.getNotes(); - } - - @Override - public String getHeadline() { - return outing.getSquad().getName() + " - " + outing.getDate(); // TODO format - } - - @Override - public String getImageUrl() { - return null; - } - - @Override - public LatLong getLatLong() { - return null; - } - - @Override - public String getWebUrl() { - return url; - } - -} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/AdminController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/AdminController.kt new file mode 100644 index 00000000..a05ad778 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/AdminController.kt @@ -0,0 +1,155 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Splitter +import com.google.common.collect.Lists +import com.google.common.collect.Sets +import org.apache.log4j.Logger +import org.springframework.stereotype.Controller +import org.springframework.validation.BindingResult +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.ModelAttribute +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.servlet.ModelAndView +import org.springframework.web.servlet.view.RedirectView +import uk.co.squadlist.web.annotations.RequiresPermission +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.context.Context +import uk.co.squadlist.web.context.GoverningBodyFactory +import uk.co.squadlist.web.exceptions.SignedInMemberRequiredException +import uk.co.squadlist.web.model.forms.InstanceDetails +import uk.co.squadlist.web.services.Permission +import uk.co.squadlist.web.services.filters.ActiveMemberFilter +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.CsvOutputRenderer +import uk.co.squadlist.web.views.DateFormatter +import uk.co.squadlist.web.views.ViewFactory +import java.util.* +import javax.servlet.http.HttpServletResponse +import javax.validation.Valid + +@Controller +class AdminController(val api: InstanceSpecificApiClient, // TODO move to user specific api + val viewFactory: ViewFactory, + val activeMemberFilter: ActiveMemberFilter, val csvOutputRenderer: CsvOutputRenderer, + val urlBuilder: UrlBuilder, + val context: Context, val dateFormatter: DateFormatter, val governingBodyFactory: GoverningBodyFactory) { + + private val log = Logger.getLogger(AdminController::class.java) + + private val MEMBER_ORDERINGS = Lists.newArrayList("firstName", "lastName") + private val GOVERNING_BODIES = Lists.newArrayList("british-rowing", "rowing-ireland") + + private val COMMA_SPLITTER = Splitter.on(",") + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @GetMapping("/admin") + @Throws(Exception::class) + fun member(): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("admin") + mv.addObject("squads", api.squads) + mv.addObject("availabilityOptions", api.availabilityOptions) + mv.addObject("title", "Admin") + + val members = api.members + mv.addObject("members", members) + mv.addObject("activeMembers", activeMemberFilter.extractActive(members)) + mv.addObject("inactiveMembers", activeMemberFilter.extractInactive(members)) + mv.addObject("admins", members.filter { it.admin!! }) + mv.addObject("governingBody", governingBodyFactory.governingBody) + mv.addObject("statistics", api.statistics()) + mv.addObject("boats", api.boats) + mv.addObject("language", context.language) + return mv + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @GetMapping("/admin/instance") + @Throws(Exception::class) + fun instance(): ModelAndView { + val instanceDetails = InstanceDetails() + instanceDetails.memberOrdering = api.instance.memberOrdering + instanceDetails.governingBody = api.instance.governingBody + return renderEditInstanceDetailsForm(instanceDetails) + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @PostMapping("/admin/instance") + @Throws(Exception::class) + fun instanceSubmit(@Valid @ModelAttribute("instanceDetails") instanceDetails: InstanceDetails, result: BindingResult): ModelAndView { + if (result.hasErrors()) { + return renderEditInstanceDetailsForm(instanceDetails) + } + + val instance = api.instance + instance.memberOrdering = instanceDetails.memberOrdering // TODO validate + instance.governingBody = instanceDetails.governingBody // TODO validate + + api.updateInstance(instance) + + return redirectToAdminScreen() + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @GetMapping("/admin/admins") + @Throws(Exception::class) + fun setAdminsPrompt(): ModelAndView { + val allMembers = api.members + val adminMembers = allMembers.filter { it.admin!! } + val availableMembers = allMembers.subtract(adminMembers) + + return viewFactory.getViewForLoggedInUser("editAdmins").addObject("admins", adminMembers).addObject("availableMembers", availableMembers) + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @PostMapping(value = "/admin/admins") + @Throws(Exception::class) + fun setAdmins(@RequestParam admins: String): ModelAndView { + log.info("Setting admins request: $admins") + val updatedAdmins = Sets.newHashSet(COMMA_SPLITTER.split(admins).iterator()) + + log.info("Setting admins to: $updatedAdmins") + api.setAdmins(updatedAdmins) + + return redirectToAdminScreen() + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @GetMapping("/admin/export/members.csv") + @Throws(Exception::class) + fun membersCSV(response: HttpServletResponse) { + val rows = Lists.newArrayList>() + for (member in api.members) { + rows.add(Arrays.asList(member.firstName, + member.lastName, + member.knownAs, + member.emailAddress, + member.gender, + if (member.dateOfBirth != null) dateFormatter.dayMonthYear(member.dateOfBirth) else "", + member.emergencyContactName, + member.emergencyContactNumber, + if (member.weight != null) member.weight!!.toString() else "", + member.sweepOarSide, + member.sculling, + member.registrationNumber, + member.rowingPoints, + member.scullingPoints, + member.role + )) + } + + csvOutputRenderer.renderCsvResponse(response, Lists.newArrayList("First name", "Last name", "Known as", "Email", + "Gender", "Date of birth", "Emergency contact name", "Emergency contact number", + "Weight", "Sweep oar side", "Sculling", "Registration number", "Rowing points", "Sculling points", "Role"), rows) + } + + private fun redirectToAdminScreen(): ModelAndView { + return ModelAndView(RedirectView(urlBuilder.adminUrl())) + } + + @Throws(SignedInMemberRequiredException::class) + private fun renderEditInstanceDetailsForm(instanceDetails: InstanceDetails): ModelAndView { + return viewFactory.getViewForLoggedInUser("editInstance").addObject("instanceDetails", instanceDetails).addObject("memberOrderings", MEMBER_ORDERINGS).addObject("governingBodies", GOVERNING_BODIES) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/AvailabilityController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/AvailabilityController.kt new file mode 100644 index 00000000..eb53cc24 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/AvailabilityController.kt @@ -0,0 +1,82 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.collect.Maps +import org.joda.time.format.ISODateTimeFormat +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.api.SquadlistApi +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.model.AvailabilityOption +import uk.co.squadlist.web.model.Outing +import uk.co.squadlist.web.model.OutingWithSquadAvailability +import uk.co.squadlist.web.services.PreferedSquadService +import uk.co.squadlist.web.services.filters.ActiveMemberFilter +import uk.co.squadlist.web.views.DateHelper +import uk.co.squadlist.web.views.ViewFactory + +@Controller +class AvailabilityController(private val instanceSpecificApiClient: InstanceSpecificApiClient, private val preferedSquadService: PreferedSquadService, + private val viewFactory: ViewFactory, + private val activeMemberFilter: ActiveMemberFilter, squadlistApiFactory: SquadlistApiFactory) { + + private val squadlistApi: SquadlistApi = squadlistApiFactory.createClient() + + @GetMapping("/availability") + fun availability(): ModelAndView { + return viewFactory.getViewForLoggedInUser("availability"). + addObject("squads", instanceSpecificApiClient.squads) + } + + @GetMapping("/availability/{squadId}") + fun squadAvailability(@PathVariable squadId: String, @RequestParam(value = "month", required = false) month: String?): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("availability") + mv.addObject("squads", instanceSpecificApiClient.squads) + + val squad = preferedSquadService.resolveSquad(squadId) + + if (squad != null) { + mv.addObject("squad", squad) + mv.addObject("title", squad.name + " availability") + mv.addObject("members", activeMemberFilter.extractActive(squadlistApi.getSquadMembers(squad.id))) + + if (squadlistApi.getSquadMembers(squad.id).isEmpty()) { + return mv + } + + var startDate = DateHelper.startOfCurrentOutingPeriod().toDate() + var endDate = DateHelper.endOfCurrentOutingPeriod().toDate() + if (month != null) { + val monthDateTime = ISODateTimeFormat.yearMonth().parseDateTime(month) // TODO Can be moved to spring? + startDate = monthDateTime.toDate() + endDate = monthDateTime.plusMonths(1).toDate() + } else { + mv.addObject("current", true) + } + + val squadAvailability = squadlistApi.getSquadAvailability(squad.id, startDate, endDate) + val outings = instanceSpecificApiClient.getSquadOutings(squad, startDate, endDate) + + mv.addObject("squadAvailability", decorateOutingsWithMembersAvailability(squadAvailability, outings)) + mv.addObject("outings", outings) + mv.addObject("outingMonths", instanceSpecificApiClient.getOutingMonths(squad)) + mv.addObject("month", month) + } + return mv + } + + private fun decorateOutingsWithMembersAvailability(squadAvailability: List, outings: List): Map { + val allAvailability = Maps.newHashMap() + for (outingWithSquadAvailability in squadAvailability) { + val outingAvailability = outingWithSquadAvailability.availability + for (member in outingAvailability.keys) { + allAvailability[outingWithSquadAvailability.outing.id + "-" + member] = outingAvailability[member] + } + } + return allAvailability + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/AvailabilityOptionsController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/AvailabilityOptionsController.kt new file mode 100644 index 00000000..d5890c07 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/AvailabilityOptionsController.kt @@ -0,0 +1,129 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Strings +import org.apache.log4j.Logger +import org.springframework.stereotype.Controller +import org.springframework.validation.BindingResult +import org.springframework.web.bind.annotation.* +import org.springframework.web.servlet.ModelAndView +import org.springframework.web.servlet.view.RedirectView +import uk.co.squadlist.web.annotations.RequiresPermission +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.exceptions.InvalidAvailabilityOptionException +import uk.co.squadlist.web.model.AvailabilityOption +import uk.co.squadlist.web.model.forms.AvailabilityOptionDetails +import uk.co.squadlist.web.services.Permission +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.ViewFactory +import javax.validation.Valid + +@Controller +class AvailabilityOptionsController(val api: InstanceSpecificApiClient, val viewFactory: ViewFactory, val urlBuilder: UrlBuilder) { + + private val log = Logger.getLogger(AvailabilityOptionsController::class.java) + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @GetMapping("/availability-option/{id}/edit") + fun editPrompt(@PathVariable id: String): ModelAndView { + val a = api.getAvailabilityOption(id) + + val availabilityOption = AvailabilityOptionDetails() + availabilityOption.name = a.label + availabilityOption.colour = a.colour + + return renderEditAvailabilityOptionForm(availabilityOption, a) + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @GetMapping("/availability-option/{id}/delete") + fun deletePrompt(@PathVariable id: String): ModelAndView { + val a = api.getAvailabilityOption(id) + return renderDeleteForm(a) + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @PostMapping("/availability-option/{id}/delete") + fun delete(@PathVariable id: String, @RequestParam(required = false) alternative: String): ModelAndView { + val a = api.getAvailabilityOption(id) + + if (!Strings.isNullOrEmpty(alternative)) { + val alternativeOption = api.getAvailabilityOption(alternative) + log.info("Deleting availability option: $a replacing with: $alternativeOption") + api.deleteAvailabilityOption(a, alternativeOption) + + } else { + log.info("Deleting availability option: $a") + api.deleteAvailabilityOption(a) + } + + return redirectToAdmin() + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @GetMapping("/availability-option/new") + fun availability(): ModelAndView { + val availabilityOption = AvailabilityOptionDetails() + availabilityOption.colour = "green" + return renderNewAvailabilityOptionForm(availabilityOption) + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @PostMapping("/availability-option/new") + fun newSquadSubmit(@Valid @ModelAttribute("availabilityOptionDetails") availabilityOptionDetails: AvailabilityOptionDetails, result: BindingResult): ModelAndView { + if (result.hasErrors()) { + return renderNewAvailabilityOptionForm(availabilityOptionDetails) + } + + try { + api.createAvailabilityOption(availabilityOptionDetails.name, availabilityOptionDetails.colour) + return redirectToAdmin() + + } catch (e: InvalidAvailabilityOptionException) { + result.rejectValue("name", null!!, e.message) + return renderNewAvailabilityOptionForm(availabilityOptionDetails) + } + + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @PostMapping("/availability-option/{id}/edit") + fun editPost(@PathVariable id: String, @Valid @ModelAttribute("availabilityOptionDetails") availabilityOptionDetails: AvailabilityOptionDetails, result: BindingResult): ModelAndView { + val a = api.getAvailabilityOption(id) + if (result.hasErrors()) { + return renderEditAvailabilityOptionForm(availabilityOptionDetails, a) + } + + a.label = availabilityOptionDetails.name + a.colour = availabilityOptionDetails.colour + + try { + api.updateAvailabilityOption(a) + + } catch (e: InvalidAvailabilityOptionException) { + result.rejectValue("name", null!!, e.message) + return renderEditAvailabilityOptionForm(availabilityOptionDetails, a) + } + + return redirectToAdmin() + } + + private fun renderNewAvailabilityOptionForm(availabilityOptionDetails: AvailabilityOptionDetails): ModelAndView { + return viewFactory.getViewForLoggedInUser("newAvailabilityOption").addObject("availabilityOptionDetails", availabilityOptionDetails) + } + + private fun renderEditAvailabilityOptionForm(availabilityOptionDetails: AvailabilityOptionDetails, a: AvailabilityOption): ModelAndView { + return viewFactory.getViewForLoggedInUser("editAvailabilityOption").addObject("availabilityOptionDetails", availabilityOptionDetails).addObject("availabilityOption", a) + } + + private fun redirectToAdmin(): ModelAndView { + return ModelAndView(RedirectView(urlBuilder.adminUrl())) + } + + private fun renderDeleteForm(a: AvailabilityOption): ModelAndView { + val alternatives = api.availabilityOptions + alternatives.remove(a) + + return viewFactory.getViewForLoggedInUser("deleteAvailabilityOption").addObject("availabilityOption", a).addObject("alternatives", alternatives) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/BoatsController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/BoatsController.kt new file mode 100644 index 00000000..08b1291d --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/BoatsController.kt @@ -0,0 +1,18 @@ +package uk.co.squadlist.web.controllers + +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.views.ViewFactory + +@Controller +class BoatsController(val api: InstanceSpecificApiClient, val viewFactory: ViewFactory){ + + @GetMapping("/boats/{id}") + fun outing(@PathVariable id: String): ModelAndView { + return viewFactory.getViewForLoggedInUser("boat").addObject("boat", api.getBoat(id)) + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/ChangePasswordController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/ChangePasswordController.kt new file mode 100644 index 00000000..43fc91bc --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/ChangePasswordController.kt @@ -0,0 +1,61 @@ +package uk.co.squadlist.web.controllers + +import org.apache.log4j.Logger +import org.springframework.stereotype.Controller +import org.springframework.validation.BindingResult +import org.springframework.validation.ObjectError +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.ModelAttribute +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.servlet.ModelAndView +import org.springframework.web.servlet.view.RedirectView +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.auth.LoggedInUserService +import uk.co.squadlist.web.model.forms.ChangePassword +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.PermissionsHelper +import uk.co.squadlist.web.views.ViewFactory +import javax.validation.Valid + +@Controller +class ChangePasswordController(val api: InstanceSpecificApiClient, val urlBuilder: UrlBuilder, val viewFactory: ViewFactory, + val loggedInUserService: LoggedInUserService, val squadlistApiFactory: SquadlistApiFactory, val permissionsHelper: PermissionsHelper) { + + private val log = Logger.getLogger(ChangePasswordController::class.java) + + @GetMapping("/change-password") + fun changePassword(): ModelAndView { + return renderChangePasswordForm(ChangePassword()) + } + + @PostMapping("/change-password") + fun changePasswordSubmit(@Valid @ModelAttribute("changePassword") changePassword: ChangePassword, result: BindingResult): ModelAndView { + if (result.hasErrors()) { + return renderChangePasswordForm(changePassword) + } + + val member = loggedInUserService.getLoggedInMember() + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.getLoggedInMembersToken()) + + log.info("Requesting change password for member: " + member.getId()) + if (loggedInUserApi.changePassword(member.getId(), changePassword.currentPassword, changePassword.newPassword)) { + return ModelAndView(RedirectView(urlBuilder.memberUrl(member))) + } else { + result.addError(ObjectError("changePassword", "Change password failed")) + return renderChangePasswordForm(changePassword) + } + } + + + private fun renderChangePasswordForm(changePassword: ChangePassword): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("changePassword") + mv.addObject("member", loggedInUserService.getLoggedInMember()) + mv.addObject("changePassword", changePassword) + mv.addObject("title", "Change password") + mv.addObject("urlBuilder", urlBuilder) + mv.addObject("permissionsHelper", permissionsHelper) + return mv + } + +} \ No newline at end of file diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/ContactsController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/ContactsController.kt new file mode 100644 index 00000000..384fa585 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/ContactsController.kt @@ -0,0 +1,48 @@ +package uk.co.squadlist.web.controllers + +import org.apache.log4j.Logger +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.services.PreferedSquadService +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.PermissionsHelper +import uk.co.squadlist.web.views.ViewFactory + +@Controller +class ContactsController(val api: InstanceSpecificApiClient, val preferedSquadService: PreferedSquadService, + val viewFactory: ViewFactory, val contactsModelPopulator: ContactsModelPopulator, + val urlBuilder: UrlBuilder, val permissionsHelper: PermissionsHelper) { + + private val log = Logger.getLogger(ContactsController::class.java) + + @GetMapping("/contacts") + fun contacts(): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("contacts") + val allSquads = api.squads + mv.addObject("squads", allSquads) // TODO leaves squad null on view + mv.addObject("urlBuilder", urlBuilder) + mv.addObject("permissionsHelper", permissionsHelper) + return mv + } + + @GetMapping("/contacts/{squadId}") + fun squadContacts(@PathVariable squadId: String): ModelAndView { + val squadToShow = preferedSquadService.resolveSquad(squadId) + + val mv = viewFactory.getViewForLoggedInUser("contacts") + val allSquads = api.squads + mv.addObject("squads", allSquads) + if (!allSquads.isEmpty()) { + log.info("Squad to show: $squadToShow") + contactsModelPopulator.populateModel(squadToShow, mv) + } + + mv.addObject("urlBuilder", urlBuilder) + mv.addObject("permissionsHelper", permissionsHelper) + return mv + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/ContactsModelPopulator.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/ContactsModelPopulator.kt new file mode 100644 index 00000000..6f5d8146 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/ContactsModelPopulator.kt @@ -0,0 +1,75 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Function +import com.google.common.base.Strings +import com.google.common.collect.Lists +import com.google.common.collect.Ordering +import com.google.common.collect.Sets +import org.springframework.stereotype.Component +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.annotations.RequiresSquadPermission +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.api.SquadlistApi +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.auth.LoggedInUserService +import uk.co.squadlist.web.model.Member +import uk.co.squadlist.web.model.Squad +import uk.co.squadlist.web.services.Permission +import uk.co.squadlist.web.services.PermissionsService +import uk.co.squadlist.web.services.filters.ActiveMemberFilter + +@Component +class ContactsModelPopulator(val instanceSpecificApiClient: InstanceSpecificApiClient, + val loggedInUserService: LoggedInUserService, + val permissionsService: PermissionsService, + val activeMemberFilter: ActiveMemberFilter, val squadlistApiFactory: SquadlistApiFactory) { + + private val squadlistApi: SquadlistApi = squadlistApiFactory.createClient() + + val role = Function { i -> i?.getRole() } + val firstName = Function { i -> i?.getFirstName() } + val lastName = Function { i -> i?.getLastName() } + + val byLastName = Ordering.natural().nullsLast().onResultOf(lastName) + val byFirstName = Ordering.natural().nullsLast().onResultOf(firstName) + + val byRole = Ordering.natural().nullsLast().onResultOf(role) + val byRoleThenFirstName = byRole.compound(byFirstName) + val byRoleThenLastName = byRole.compound(byLastName) + + + @RequiresSquadPermission(permission = Permission.VIEW_SQUAD_CONTACT_DETAILS) + fun populateModel(squad: Squad, mv: ModelAndView) { + mv.addObject("title", squad.name + " contacts") + mv.addObject("squad", squad) + + val instance = instanceSpecificApiClient.instance + val byRoleThenName = if (instance.memberOrdering != null && instance.memberOrdering == "firstName") byRoleThenFirstName else byRoleThenLastName + + val activeMembers = byRoleThenName.sortedCopy(activeMemberFilter.extractActive(squadlistApi.getSquadMembers(squad.id))) + val redactedMembers = redactContentDetailsForMembers(loggedInUserService.loggedInMember, activeMembers) + mv.addObject("members", redactedMembers) + + val emails = Sets.newHashSet() + for (member in redactedMembers) { + if (!Strings.isNullOrEmpty(member.emailAddress)) { + emails.add(member.emailAddress) + } + } + if (!emails.isEmpty()) { + mv.addObject("emails", Lists.newArrayList(emails)) + } + } + + private fun redactContentDetailsForMembers(loggedInMember: Member, members: List): List { + val redactedMembers = Lists.newArrayList() + for (member in members) { + if (!permissionsService.canSeePhoneNumberForRower(loggedInMember, member)) { + member.contactNumber = null + } + redactedMembers.add(member) + } + return redactedMembers + } + +} \ No newline at end of file diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/EntryDetailsController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/EntryDetailsController.kt new file mode 100644 index 00000000..8f0ab17a --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/EntryDetailsController.kt @@ -0,0 +1,111 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Splitter +import com.google.common.collect.Lists +import org.codehaus.jackson.map.ObjectMapper +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.* +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.auth.LoggedInUserService +import uk.co.squadlist.web.context.GoverningBodyFactory +import uk.co.squadlist.web.services.PreferedSquadService +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.CsvOutputRenderer +import uk.co.squadlist.web.views.DateFormatter +import uk.co.squadlist.web.views.PermissionsHelper +import uk.co.squadlist.web.views.ViewFactory +import java.util.* +import javax.servlet.http.HttpServletResponse + +@Controller +public class EntryDetailsController(val instanceSpecificApiClient: InstanceSpecificApiClient, + val preferedSquadService: PreferedSquadService, + val viewFactory: ViewFactory, + val entryDetailsModelPopulator: EntryDetailsModelPopulator, + val csvOutputRenderer: CsvOutputRenderer, val governingBodyFactory: GoverningBodyFactory, + val squadlistApiFactory: SquadlistApiFactory, val loggedInUserService: LoggedInUserService, + val urlBuilder: UrlBuilder, val permissionsHelper: PermissionsHelper, val dateFormatter: DateFormatter) { + + private val entryDetailsHeaders = Lists.newArrayList("First name", "Last name", "Date of birth", "Effective age", "Age grade", + "Weight", "Rowing points", "Rowing status", + "Sculling points", "Sculling status", "Registration number") + + @GetMapping("/entry-details/{squadId}") + fun entrydetails(@PathVariable squadId: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val mv = viewFactory.getViewForLoggedInUser("entryDetails") + mv.addObject("squads", instanceSpecificApiClient.squads) + mv.addObject("governingBody", governingBodyFactory.governingBody) + mv.addObject("urlBuilder", urlBuilder) + mv.addObject("permissionsHelper", permissionsHelper) + + val squadToShow = preferedSquadService.resolveSquad(squadId) + entryDetailsModelPopulator.populateModel(squadToShow, mv, loggedInUserApi) + return mv.addObject("dateFormatter", dateFormatter) + } + + @PostMapping("/entry-details/ajax") + fun ajax(@RequestBody json: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val memberIds = ObjectMapper().readTree(json).map { it.asText() } + val selectedMembers= memberIds.map { loggedInUserApi.getMember(it)} + + val rowingPoints = selectedMembers.map { it.rowingPoints } + val scullingPoints = selectedMembers.map { it.scullingPoints } + + val mv = viewFactory.getViewForLoggedInUser("entryDetailsAjax") + if (!selectedMembers.isEmpty()) { + mv.addObject("members", selectedMembers) + + val governingBody = governingBodyFactory.governingBody + + val crewSize = selectedMembers.size + val isFullBoat = governingBody.boatSizes.contains(crewSize) + mv.addObject("ok", isFullBoat) + if (isFullBoat) { + mv.addObject("rowingPoints", governingBody.getTotalPoints(rowingPoints)) + mv.addObject("rowingStatus", governingBody.getRowingStatus(rowingPoints)) + + mv.addObject("scullingPoints", governingBody.getTotalPoints(scullingPoints)) + mv.addObject("scullingStatus", governingBody.getScullingStatus(scullingPoints)) + + val datesOfBirth = Lists.newArrayList() + for (member in selectedMembers) { + datesOfBirth.add(member.dateOfBirth) + } + + val effectiveAge = governingBody.getEffectiveAge(datesOfBirth) + if (effectiveAge != null) { + mv.addObject("effectiveAge", effectiveAge) + mv.addObject("ageGrade", governingBody.getAgeGrade(effectiveAge)) + } + } + } + return mv + } + + @GetMapping("/entry-details/{squadId}.csv") + fun entrydetailsCSV(@PathVariable squadId: String, response: HttpServletResponse) { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val squadToShow = preferedSquadService.resolveSquad(squadId) + val squadMembers = loggedInUserApi.getSquadMembers(squadToShow.id) + + val entryDetailsRows = entryDetailsModelPopulator.getEntryDetailsRows(squadMembers) + csvOutputRenderer.renderCsvResponse(response, entryDetailsHeaders, entryDetailsRows) + } + + @GetMapping("/entry-details/selected.csv") // TODO Unused + fun entrydetailsSelectedCSV(@RequestParam members: String, response: HttpServletResponse) { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val selectedMembers = Splitter.on(",").split(members).map { loggedInUserApi.getMember(it) } + + csvOutputRenderer.renderCsvResponse(response, entryDetailsHeaders, entryDetailsModelPopulator.getEntryDetailsRows(selectedMembers)) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/EntryDetailsModelPopulator.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/EntryDetailsModelPopulator.kt new file mode 100644 index 00000000..fc302659 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/EntryDetailsModelPopulator.kt @@ -0,0 +1,55 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.collect.Lists +import org.springframework.stereotype.Component +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.annotations.RequiresSquadPermission +import uk.co.squadlist.web.api.SquadlistApi +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.context.GoverningBodyFactory +import uk.co.squadlist.web.model.Member +import uk.co.squadlist.web.model.Squad +import uk.co.squadlist.web.services.Permission +import uk.co.squadlist.web.services.filters.ActiveMemberFilter +import uk.co.squadlist.web.views.DateFormatter +import java.util.* + +@Component +class EntryDetailsModelPopulator (val dateFormatter: DateFormatter, val activeMemberFilter: ActiveMemberFilter, + val governingBodyFactory: GoverningBodyFactory, squadlistApiFactory: SquadlistApiFactory) { + + @RequiresSquadPermission(permission = Permission.VIEW_SQUAD_ENTRY_DETAILS) + fun populateModel(squadToShow: Squad, mv: ModelAndView, squadlistApi: SquadlistApi) { + mv.addObject("squad", squadToShow) + mv.addObject("title", squadToShow.name + " entry details") + mv.addObject("members", activeMemberFilter.extractActive(squadlistApi.getSquadMembers(squadToShow.id))) + } + + @RequiresSquadPermission(permission = Permission.VIEW_SQUAD_ENTRY_DETAILS) + fun getEntryDetailsRows(squadToShow: Squad, squadlistApi: SquadlistApi): List> { + return getEntryDetailsRows(activeMemberFilter.extractActive(squadlistApi.getSquadMembers(squadToShow.id))) + } + + fun getEntryDetailsRows(members: List): List> { // TOOD permissions + val governingBody = governingBodyFactory.governingBody + + val rows = Lists.newArrayList>() + for (member in members) { + val effectiveAge = if (member.dateOfBirth != null) governingBody.getEffectiveAge(member.dateOfBirth) else null + val ageGrade = if (effectiveAge != null) governingBody.getAgeGrade(effectiveAge) else null + + rows.add(Arrays.asList(member.firstName, member.lastName, + if (member.dateOfBirth != null) dateFormatter.dayMonthYear(member.dateOfBirth) else "", + effectiveAge?.toString() ?: "", + ageGrade ?: "", + if (member.weight != null) member.weight!!.toString() else "", + member.rowingPoints, + governingBody.getRowingStatus(member.rowingPoints), + member.scullingPoints, + governingBody.getScullingStatus(member.scullingPoints), + member.registrationNumber)) + } + return rows + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/GoverningBodyController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/GoverningBodyController.kt new file mode 100644 index 00000000..b5f15591 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/GoverningBodyController.kt @@ -0,0 +1,32 @@ +package uk.co.squadlist.web.controllers + +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.api.ApiUrlBuilder +import uk.co.squadlist.web.context.GoverningBodyFactory +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.PermissionsHelper +import uk.co.squadlist.web.views.ViewFactory + +@Controller +public class GoverningBodyController(val governingBodyFactory: GoverningBodyFactory, val viewFactory: ViewFactory, + val urlBuilder: UrlBuilder, val permissionsHelper: PermissionsHelper) { + + @GetMapping("/governing-body/british-rowing") + fun governingBody(): ModelAndView { + val governingBody = governingBodyFactory.governingBody + + return viewFactory.getViewForLoggedInUser("governingBody").addAllObjects( + mapOf("governingBody" to governingBody, + "title" to governingBody.name, + "ageGrades" to governingBody.ageGrades, + "statuses" to governingBody.statusPoints, + "boatSizes" to governingBody.boatSizes, + "urlBuilder" to urlBuilder, + "permissionsHelper" to permissionsHelper) + ) + } + +} + diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/LoginController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/LoginController.kt new file mode 100644 index 00000000..c601dfe8 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/LoginController.kt @@ -0,0 +1,53 @@ +package uk.co.squadlist.web.controllers + +import org.apache.log4j.Logger +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.* +import org.springframework.web.servlet.ModelAndView +import org.springframework.web.servlet.view.RedirectView +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.auth.LoggedInUserService +import uk.co.squadlist.web.urls.UrlBuilder + +@Controller +class LoginController(val api: InstanceSpecificApiClient, val loggedInUserService: LoggedInUserService, val urlBuilder: UrlBuilder) { + + private val log = Logger.getLogger(LoginController::class.java) + + @RequestMapping(value = "/login", method = arrayOf(RequestMethod.GET, RequestMethod.HEAD)) // TODO SEO this onto the root url + fun login() = renderLoginScreen() + + @PostMapping("/login") + fun loginSubmit( + @RequestParam(value = "username", required = true) username: String, + @RequestParam(value = "password", required = true) password: String): ModelAndView { + + log.info("Attempting to auth user: $username") + api.auth(username, password)?.let { token -> + api.verify(token)?.let { user -> + log.info("Auth successful for user: " + user.username) + loggedInUserService.setSignedIn(token) + return ModelAndView(RedirectView(urlBuilder.getBaseUrl())) + } + } + + return renderLoginScreen(true, username) + } + + @GetMapping("/logout") + fun logout(): ModelAndView { + loggedInUserService.cleanSignedIn() + return ModelAndView(RedirectView(urlBuilder.loginUrl())) + } + + private fun renderLoginScreen(errors: Boolean = false, username: String? = null): ModelAndView { + val instance = api.instance + return ModelAndView("login").addAllObjects(mapOf( + "title" to instance.name, + "username" to username, + "errors" to errors, + "urlBuilder" to urlBuilder + )) + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/MemberController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/MemberController.kt new file mode 100644 index 00000000..9b621df2 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/MemberController.kt @@ -0,0 +1,388 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Strings +import com.google.common.collect.Lists +import org.apache.log4j.Logger +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import org.joda.time.IllegalFieldValueException +import org.springframework.stereotype.Controller +import org.springframework.validation.BindingResult +import org.springframework.validation.ObjectError +import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartHttpServletRequest +import org.springframework.web.servlet.ModelAndView +import org.springframework.web.servlet.view.RedirectView +import uk.co.squadlist.web.annotations.RequiresMemberPermission +import uk.co.squadlist.web.annotations.RequiresPermission +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.auth.LoggedInUserService +import uk.co.squadlist.web.context.GoverningBodyFactory +import uk.co.squadlist.web.exceptions.InvalidImageException +import uk.co.squadlist.web.exceptions.InvalidMemberException +import uk.co.squadlist.web.exceptions.UnknownSquadException +import uk.co.squadlist.web.model.Member +import uk.co.squadlist.web.model.Squad +import uk.co.squadlist.web.model.forms.MemberDetails +import uk.co.squadlist.web.model.forms.MemberSquad +import uk.co.squadlist.web.services.PasswordGenerator +import uk.co.squadlist.web.services.Permission +import uk.co.squadlist.web.services.PermissionsService +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.ViewFactory +import java.util.* +import javax.validation.Valid + +@Controller +public class MembersController(val instanceSpecificApiClient: InstanceSpecificApiClient, val loggedInUserService: LoggedInUserService, val urlBuilder: UrlBuilder, + val viewFactory: ViewFactory, + val passwordGenerator: PasswordGenerator, + val permissionsService: PermissionsService, + val governingBodyFactory: GoverningBodyFactory, + val squadlistApiFactory: SquadlistApiFactory) { + + private val log = Logger.getLogger(MembersController::class.java) + + private val GENDER_OPTIONS = listOf("female", "male") + private val ROLES_OPTIONS = listOf("Rower", "Rep", "Coach", "Cox", "Non rowing") + private val SWEEP_OAR_SIDE_OPTIONS = listOf("Bow", "Stroke", "Bow/Stroke", "Stroke/Bow") + private val YES_NO_OPTIONS = listOf("Y", "N") + + private val squadlistApi = squadlistApiFactory.createClient() + + @RequiresMemberPermission(permission = Permission.VIEW_MEMBER_DETAILS) + @GetMapping("/member/{id}") + fun member(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val members = loggedInUserApi.getMember(id) + + val mv = viewFactory.getViewForLoggedInUser("memberDetails") + mv.addObject("member", members) + mv.addObject("title", members.firstName + " " + members.lastName) + mv.addObject("governingBody", governingBodyFactory.governingBody) + return mv + } + + @RequiresPermission(permission = Permission.ADD_MEMBER) + @GetMapping("/member/new") + fun newMember(@ModelAttribute("memberDetails") memberDetails: MemberDetails): ModelAndView { + return renderNewMemberForm() + } + + @RequiresPermission(permission = Permission.ADD_MEMBER) + @PostMapping("/member/new") + fun newMemberSubmit(@Valid @ModelAttribute("memberDetails") memberDetails: MemberDetails, result: BindingResult): ModelAndView { + val requestedSquads = extractAndValidateRequestedSquads(memberDetails, result) + + if (result.hasErrors()) { + log.info("New member submission has errors: " + result.allErrors) + return renderNewMemberForm() + } + + val initialPassword = passwordGenerator.generateRandomPassword(10) + + try { + val newMember = instanceSpecificApiClient.createMember(memberDetails.firstName, + memberDetails.lastName, + requestedSquads, + memberDetails.emailAddress, + initialPassword, + null, + memberDetails.role + ) + + return viewFactory.getViewForLoggedInUser("memberAdded").addObject("member", newMember).addObject("initialPassword", initialPassword) + + } catch (e: InvalidMemberException) { + log.warn("Invalid member exception: " + e.message) + result.addError(ObjectError("memberDetails", e.message)) + return renderNewMemberForm() + } + + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @GetMapping("/member/{id}/edit") + fun updateMember(@PathVariable id: String, @RequestParam(required = false) invalidImage: Boolean?): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val member = loggedInUserApi.getMember(id) + + val memberDetails = MemberDetails() + memberDetails.firstName = member.firstName + memberDetails.lastName = member.lastName + memberDetails.knownAs = member.knownAs + memberDetails.gender = member.gender + + memberDetails.dateOfBirthDay = if (member.dateOfBirth != null) DateTime(member.dateOfBirth).dayOfMonth else null + memberDetails.dateOfBirthMonth = if (member.dateOfBirth != null) DateTime(member.dateOfBirth).monthOfYear else null + memberDetails.dateOfBirthYear = if (member.dateOfBirth != null) DateTime(member.dateOfBirth).year else null + + memberDetails.weight = if (member.weight != null) Integer.toString(member.weight!!) else null + memberDetails.emailAddress = member.emailAddress + memberDetails.contactNumber = member.contactNumber + memberDetails.registrationNumber = member.registrationNumber + memberDetails.rowingPoints = member.rowingPoints + memberDetails.sculling = member.sculling + memberDetails.scullingPoints = member.scullingPoints + memberDetails.sweepOarSide = member.sweepOarSide + + memberDetails.postcode = if (member.address != null) member.address["postcode"] else null + + val memberSquads = Lists.newArrayList() + for (squad in member.squads) { + memberSquads.add(MemberSquad(squad.id)) + } + + memberDetails.squads = memberSquads + memberDetails.emergencyContactName = member.emergencyContactName + memberDetails.emergencyContactNumber = member.emergencyContactNumber + memberDetails.role = member.role + memberDetails.profileImage = member.profileImage + + val mv = renderEditMemberDetailsForm(memberDetails, member.id, member.firstName + " " + member.lastName, member) + mv.addObject("invalidImage", invalidImage) + return mv + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @PostMapping("/member/{id}/edit") + fun updateMemberSubmit(@PathVariable id: String, @Valid @ModelAttribute("member") memberDetails: MemberDetails, result: BindingResult): ModelAndView { + log.info("Received edit member request: $memberDetails") + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val member = loggedInUserApi.getMember(id) + + val squads = extractAndValidateRequestedSquads(memberDetails, result) + if (!Strings.isNullOrEmpty(memberDetails.scullingPoints)) { + if (!governingBodyFactory.governingBody.pointsOptions.contains(memberDetails.scullingPoints)) { + result.addError(ObjectError("member.scullingPoints", "Invalid points option")) + } + } + if (!Strings.isNullOrEmpty(memberDetails.rowingPoints)) { + if (!governingBodyFactory.governingBody.pointsOptions.contains(memberDetails.rowingPoints)) { + result.addError(ObjectError("member.rowingPoints", "Invalid points option")) + } + } + + var updatedDateOfBirth: Date? = null + if (memberDetails.dateOfBirthDay != null && + memberDetails.dateOfBirthMonth != null && + memberDetails.dateOfBirthYear != null) { + try { + updatedDateOfBirth = DateTime(memberDetails.dateOfBirthYear!!, + memberDetails.dateOfBirthMonth!!, memberDetails.dateOfBirthDay!!, 0, 0, 0, + DateTimeZone.UTC).toDate() + member.dateOfBirth = updatedDateOfBirth + } catch (e: IllegalFieldValueException) { + result.addError(ObjectError("member.dateOfBirthYear", "Invalid date")) + } + + } + + if (result.hasErrors()) { + return renderEditMemberDetailsForm(memberDetails, member.id, member.firstName + " " + member.lastName, member) + } + + log.info("Updating member details: " + member.id) + member.firstName = memberDetails.firstName + member.lastName = memberDetails.lastName + member.knownAs = memberDetails.knownAs + member.gender = memberDetails.gender + + member.dateOfBirth = updatedDateOfBirth + + try { + member.weight = if (!Strings.isNullOrEmpty(memberDetails.weight)) Integer.parseInt(memberDetails.weight) else null // TODO validate + } catch (iae: IllegalArgumentException) { + log.warn(iae) + } + + member.emailAddress = memberDetails.emailAddress + member.contactNumber = memberDetails.contactNumber + member.rowingPoints = if (!Strings.isNullOrEmpty(memberDetails.rowingPoints)) memberDetails.rowingPoints else null + member.sculling = memberDetails.sculling + member.scullingPoints = if (!Strings.isNullOrEmpty(memberDetails.scullingPoints)) memberDetails.scullingPoints else null + member.registrationNumber = memberDetails.registrationNumber + member.emergencyContactName = memberDetails.emergencyContactName + member.emergencyContactNumber = memberDetails.emergencyContactNumber + member.sweepOarSide = memberDetails.sweepOarSide + + val canChangeRole = permissionsService.canChangeRoleFor(loggedInUserService.loggedInMember, member) + if (canChangeRole) { + member.role = memberDetails.role + } + + if (canChangeRole) { + member.squads = squads + } + + member.address["postcode"] = memberDetails.postcode + + log.info("Submitting updated member: $member") + loggedInUserApi.updateMemberDetails(member) + return ModelAndView(RedirectView(urlBuilder.memberUrl(member))) + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @GetMapping("/member/{id}/make-inactive") + fun makeInactivePrompt(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val member = loggedInUserApi.getMember(id) + return viewFactory.getViewForLoggedInUser("makeMemberInactivePrompt").addObject("member", member).addObject("title", "Make member inactive - " + member.displayName) + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @PostMapping("/member/{id}/make-inactive") + fun makeInactive(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + log.info("Making member inactive: $id") + val member = loggedInUserApi.getMember(id) + member.inactive = true + loggedInUserApi.updateMemberDetails(member) + + return redirectToAdminScreen() + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @GetMapping("/member/{id}/delete") + fun deletePrompt(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val member = loggedInUserApi.getMember(id) + return viewFactory.getViewForLoggedInUser("deleteMemberPrompt").addObject("member", member).addObject("title", "Delete member - " + member.displayName) + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @PostMapping("/member/{id}/delete") + fun delete(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val member = loggedInUserApi.getMember(id) + + loggedInUserApi.deleteMember(member) + return redirectToAdminScreen() + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @GetMapping("/member/{id}/make-active") + fun makeActivePrompt(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val member = loggedInUserApi.getMember(id) + + return viewFactory.getViewForLoggedInUser("makeMemberActivePrompt").addObject("member", member).addObject("title", "Make member active - " + member.displayName) + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @PostMapping("/member/{id}/make-active") + fun makeActive(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + log.info("Making member active: $id") + val member = loggedInUserApi.getMember(id) + member.inactive = false + loggedInUserApi.updateMemberDetails(member) + return redirectToAdminScreen() + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @PostMapping("/member/{id}/edit/profileimage") + fun updateMemberProfileImageSubmit(@PathVariable id: String, request: MultipartHttpServletRequest): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + log.info("Received update member profile image request: $id") + val member = loggedInUserApi.getMember(id) + + val file = request.getFile("image") + + log.info("Submitting updated member: $member") + try { + loggedInUserApi.updateMemberProfileImage(member, file!!.bytes) + } catch (e: InvalidImageException) { + log.warn("Invalid image file submitted") + return ModelAndView(RedirectView(urlBuilder.editMemberUrl(member) + "?invalidImage=true")) + } + + return ModelAndView(RedirectView(urlBuilder.memberUrl(member))) + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @GetMapping("/member/{id}/reset") + fun resetMemberPasswordPrompt(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + val member = loggedInUserApi.getMember(id) + return viewFactory.getViewForLoggedInUser("memberPasswordResetPrompt").addObject("member", member) + } + + @RequiresMemberPermission(permission = Permission.EDIT_MEMBER_DETAILS) + @PostMapping("/member/{id}/reset") + fun resetMemberPassword(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val member = loggedInUserApi.getMember(id) + + val newPassword = instanceSpecificApiClient.resetMemberPassword(member) + + return viewFactory.getViewForLoggedInUser("memberPasswordReset").addObject("member", member).addObject("password", newPassword) + } + + private fun redirectToAdminScreen(): ModelAndView { + return ModelAndView(RedirectView(urlBuilder.adminUrl())) + } + + private fun renderNewMemberForm(): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("newMember") + mv.addObject("squads", instanceSpecificApiClient.squads) + mv.addObject("title", "Adding a new member") + mv.addObject("rolesOptions", ROLES_OPTIONS) + return mv + } + + private fun renderEditMemberDetailsForm(memberDetails: MemberDetails, memberId: String, title: String, member: Member): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("editMemberDetails") + mv.addObject("member", memberDetails) + mv.addObject("memberId", memberId) + mv.addObject("title", title) + mv.addObject("squads", instanceSpecificApiClient.squads) + mv.addObject("governingBody", governingBodyFactory.governingBody) + + mv.addObject("genderOptions", GENDER_OPTIONS) + mv.addObject("pointsOptions", governingBodyFactory.governingBody.pointsOptions) + mv.addObject("rolesOptions", ROLES_OPTIONS) + mv.addObject("sweepOarSideOptions", SWEEP_OAR_SIDE_OPTIONS) + mv.addObject("yesNoOptions", YES_NO_OPTIONS) + + val canChangeRole = permissionsService.canChangeRoleFor(loggedInUserService.loggedInMember, member) + mv.addObject("canChangeRole", canChangeRole) + mv.addObject("canChangeSquads", canChangeRole) + + mv.addObject("memberSquads", member.squads) // TODO would not be needed id member.squads would form bind + return mv + } + + private fun extractAndValidateRequestedSquads(memberDetails: MemberDetails, result: BindingResult): List { + val squads = Lists.newArrayList() + if (memberDetails.squads == null) { + return squads + } + + for (requestedSquad in memberDetails.squads) { + log.info("Requested squad: $requestedSquad") + try { + squads.add(squadlistApi.getSquad(requestedSquad.id)) // TODO Validate instance + } catch (e: UnknownSquadException) { + log.warn("Rejecting unknown squad: $requestedSquad") + result.addError(ObjectError("memberDetails.squad", "Unknown squad")) + } + + } + log.info("Assigned squads: $squads") + return squads + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/MyOutingsController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/MyOutingsController.kt new file mode 100644 index 00000000..3afe73f0 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/MyOutingsController.kt @@ -0,0 +1,86 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Strings +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.annotations.RequiresSignedInMember +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.auth.LoggedInUserService +import uk.co.squadlist.web.exceptions.SignedInMemberRequiredException +import uk.co.squadlist.web.exceptions.UnknownMemberException +import uk.co.squadlist.web.services.OutingAvailabilityCountsService +import uk.co.squadlist.web.services.ical.OutingCalendarService +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.DateHelper +import uk.co.squadlist.web.views.PermissionsHelper +import uk.co.squadlist.web.views.ViewFactory +import javax.servlet.http.HttpServletResponse + +@Controller +class MyOutingsController(val loggedInUserService: LoggedInUserService, val instanceSpecificApiClient: InstanceSpecificApiClient, val viewFactory: ViewFactory, + val outingAvailabilityCountsService: OutingAvailabilityCountsService, private val urlBuilder: UrlBuilder, + val outingCalendarService: OutingCalendarService, val squadlistApiFactory: SquadlistApiFactory, val permissionsHelper: PermissionsHelper) { + + private val squadlistApi = squadlistApiFactory.createClient() + + @RequiresSignedInMember + @RequestMapping("/") + @Throws(SignedInMemberRequiredException::class) + fun outings(): ModelAndView { + val loggedInUser = loggedInUserService.loggedInMember.id + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val mv = viewFactory.getViewForLoggedInUser("myOutings") + mv.addObject("member", loggedInUserApi.getMember(loggedInUser)) + + val startDate = DateHelper.startOfCurrentOutingPeriod().toDate() + val endDate = DateHelper.oneYearFromNow().toDate() + + mv.addObject("outings", loggedInUserApi.getAvailabilityFor(loggedInUser, startDate, endDate)) + + mv.addObject("title", "My outings") + mv.addObject("availabilityOptions", instanceSpecificApiClient.availabilityOptions) + mv.addObject("rssUrl", urlBuilder.outingsRss(loggedInUser, instanceSpecificApiClient.instance)) + mv.addObject("icalUrl", urlBuilder.outingsIcal(loggedInUser, instanceSpecificApiClient.instance)) + mv.addObject("permissionsHelper", permissionsHelper) + mv.addObject("urlBuilder", urlBuilder) + return mv + } + + @RequestMapping("/ical") + fun outingsIcal(@RequestParam(value = "user", required = false) user: String, response: HttpServletResponse) { + if (Strings.isNullOrEmpty(user)) { + throw UnknownMemberException() + } + + val member = squadlistApi.getMember(user) + + val calendar = outingCalendarService.buildCalendarFor(squadlistApi.getAvailabilityFor(member.id, + DateHelper.startOfCurrentOutingPeriod().toDate(), + DateHelper.oneYearFromNow().toDate()), + instanceSpecificApiClient.instance) + + response.status = HttpServletResponse.SC_OK + response.contentType = "text/calendar" + + val writer = response.writer + writer.println(calendar.toString()) + writer.flush() + } + + @RequiresSignedInMember + @RequestMapping("/myoutings/ajax") + fun ajax(): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("myOutingsAjax") + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + val pendingOutingsCountFor = outingAvailabilityCountsService.getPendingOutingsCountFor(loggedInUserService.loggedInMember.id, loggedInUserApi) + if (pendingOutingsCountFor > 0) { + mv.addObject("pendingOutingsCount", pendingOutingsCountFor) + } + return mv + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/OutingsController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/OutingsController.kt new file mode 100644 index 00000000..847fa211 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/OutingsController.kt @@ -0,0 +1,324 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Strings +import com.google.common.collect.Lists +import org.apache.log4j.Logger +import org.codehaus.jackson.JsonParseException +import org.codehaus.jackson.map.JsonMappingException +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import org.joda.time.LocalDateTime +import org.joda.time.format.ISODateTimeFormat +import org.springframework.stereotype.Controller +import org.springframework.validation.BindingResult +import org.springframework.validation.ObjectError +import org.springframework.web.bind.annotation.* +import org.springframework.web.servlet.ModelAndView +import org.springframework.web.servlet.view.RedirectView +import uk.co.eelpieconsulting.common.http.HttpFetchException +import uk.co.squadlist.web.annotations.RequiresOutingPermission +import uk.co.squadlist.web.annotations.RequiresPermission +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.api.SquadlistApi +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.auth.LoggedInUserService +import uk.co.squadlist.web.exceptions.InvalidOutingException +import uk.co.squadlist.web.exceptions.OutingClosedException +import uk.co.squadlist.web.exceptions.UnknownAvailabilityOptionException +import uk.co.squadlist.web.model.AvailabilityOption +import uk.co.squadlist.web.model.Instance +import uk.co.squadlist.web.model.Outing +import uk.co.squadlist.web.model.Squad +import uk.co.squadlist.web.model.forms.OutingDetails +import uk.co.squadlist.web.services.OutingAvailabilityCountsService +import uk.co.squadlist.web.services.Permission +import uk.co.squadlist.web.services.PreferedSquadService +import uk.co.squadlist.web.services.filters.ActiveMemberFilter +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.CsvOutputRenderer +import uk.co.squadlist.web.views.DateFormatter +import uk.co.squadlist.web.views.DateHelper +import uk.co.squadlist.web.views.ViewFactory +import java.io.IOException +import java.util.* +import javax.servlet.http.HttpServletResponse +import javax.validation.Valid + +@Controller +class OutingsController(val loggedInUserService: LoggedInUserService, val instanceSpecificApiClient: InstanceSpecificApiClient, val urlBuilder: UrlBuilder, + val dateFormatter: DateFormatter, val preferedSquadService: PreferedSquadService, val viewFactory: ViewFactory, + val outingAvailabilityCountsService: OutingAvailabilityCountsService, val activeMemberFilter: ActiveMemberFilter, + val csvOutputRenderer: CsvOutputRenderer, val squadlistApiFactory: SquadlistApiFactory) { + + private val log = Logger.getLogger(OutingsController::class.java) + + @GetMapping("/outings") + fun outings(@RequestParam(required = false, value = "squad") squadId: String, + @RequestParam(value = "month", required = false) month: String?): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val squadToShow = preferedSquadService.resolveSquad(squadId) + val mv = viewFactory.getViewForLoggedInUser("outings") + if (squadToShow == null) { + mv.addObject("title", "Outings") + return mv + } + + var startDate = DateHelper.startOfCurrentOutingPeriod().toDate() + var endDate = DateHelper.endOfCurrentOutingPeriod().toDate() + + var title = squadToShow.name + " outings" + if (month != null) { + val monthDateTime = ISODateTimeFormat.yearMonth().parseDateTime(month) // TODO Can be moved to spring? + startDate = monthDateTime.toDate() + endDate = monthDateTime.plusMonths(1).toDate() + title = squadToShow.name + " outings - " + dateFormatter.fullMonthYear(startDate) + } else { + mv.addObject("current", true) + } + + mv.addObject("title", title) + mv.addObject("squad", squadToShow) + mv.addObject("startDate", startDate) + mv.addObject("endDate", endDate) + mv.addObject("month", month) + mv.addObject("outingMonths", getOutingMonthsFor(squadToShow)) + + val squadOutings = loggedInUserApi.getSquadAvailability(squadToShow.id, startDate, endDate) + mv.addObject("outings", squadOutings) + mv.addObject("outingAvailabilityCounts", outingAvailabilityCountsService.buildOutingAvailabilityCounts(squadOutings)) + + mv.addObject("squads", instanceSpecificApiClient.squads) + return mv + } + + @GetMapping("/outings/{id}") + fun outing(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val outing = loggedInUserApi.getOuting(id) + + val mv = viewFactory.getViewForLoggedInUser("outing") + mv.addObject("title", outing.squad.name + " - " + dateFormatter.dayMonthYearTime(outing.date)) + mv.addObject("outing", outing) + mv.addObject("outingMonths", getOutingMonthsFor(outing.squad)) + mv.addObject("squad", outing.squad) + mv.addObject("squadAvailability", loggedInUserApi.getOutingAvailability(outing.id)) + mv.addObject("squads", instanceSpecificApiClient.squads) + mv.addObject("members", activeMemberFilter.extractActive(loggedInUserApi.getSquadMembers(outing.squad.id))) + mv.addObject("month", ISODateTimeFormat.yearMonth().print(outing.date.time)) // TODO push to date parser - local time + return mv + } + + @GetMapping("/outings/{id}.csv") + fun outingCsv(@PathVariable id: String, response: HttpServletResponse) { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val outing = loggedInUserApi.getOuting(id) + val squadMembers = loggedInUserApi.getSquadMembers(outing.squad.id) + val outingAvailability = loggedInUserApi.getOutingAvailability(outing.id) + + val rows = Lists.newArrayList>() + for (member in squadMembers) { + rows.add(Arrays.asList( + dateFormatter.dayMonthYearTime(outing.date), + outing.squad.name, + member.displayName, + member.role, + if (outingAvailability[member.id] != null) outingAvailability[member.id]!!.getLabel() else null + )) + } + csvOutputRenderer.renderCsvResponse(response, Lists.newArrayList("Date", "Squad", "Member", "Role", "Availability"), rows) + } + + @RequiresPermission(permission = Permission.ADD_OUTING) + @GetMapping("/outings/new") // TODO fails hard if no squads are available + fun newOuting(): ModelAndView { + val defaultOutingDateTime = DateHelper.defaultOutingStartDateTime() + val outingDefaults = OutingDetails(defaultOutingDateTime) + outingDefaults.squad = preferedSquadService.resolveSquad(null).id + return renderNewOutingForm(outingDefaults) + } + + @RequiresPermission(permission = Permission.ADD_OUTING) + @PostMapping("/outings/new") + fun newOutingSubmit(@Valid @ModelAttribute("outing") outingDetails: OutingDetails, result: BindingResult): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + if (result.hasErrors()) { + return renderNewOutingForm(outingDetails) + } + + try { + val newOuting = buildOutingFromOutingDetails(outingDetails, instanceSpecificApiClient.instance, loggedInUserApi) + if (outingDetails.repeats != null && outingDetails.repeats!! && outingDetails.repeatsCount != null) { + loggedInUserApi.createOuting(newOuting, outingDetails.repeatsCount) + } else { + loggedInUserApi.createOuting(newOuting, null) + } + + val outingsViewForNewOutingsSquadAndMonth = urlBuilder.outings(newOuting.squad, DateTime(newOuting.date).toString("yyyy-MM")) + return ModelAndView(RedirectView(outingsViewForNewOutingsSquadAndMonth)) + + } catch (e: InvalidOutingException) { + result.addError(ObjectError("outing", e.message)) + return renderNewOutingForm(outingDetails) + + } catch (e: Exception) { + result.addError(ObjectError("outing", "Unknown error")) + return renderNewOutingForm(outingDetails) + } + + } + + @RequiresOutingPermission(permission = Permission.EDIT_OUTING) + @GetMapping("/outings/{id}/edit") + fun outingEdit(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val outing = loggedInUserApi.getOuting(id) + + val outingDetails = OutingDetails(LocalDateTime(outing.date)) + outingDetails.squad = outing.squad.id + outingDetails.notes = outing.notes + + return renderEditOutingForm(outingDetails, outing) + } + + @RequiresOutingPermission(permission = Permission.EDIT_OUTING) + @RequestMapping("/outings/{id}/delete", method = arrayOf(RequestMethod.GET)) + fun deleteOutingPrompt(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + val outing = loggedInUserApi.getOuting(id) + return viewFactory.getViewForLoggedInUser("deleteOuting").addObject("outing", outing) + } + + @RequiresOutingPermission(permission = Permission.EDIT_OUTING) + @PostMapping("/outings/{id}/delete") + fun deleteOuting(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + val outing = loggedInUserApi.getOuting(id) + + loggedInUserApi.deleteOuting(outing.id) + + val exitUrl = if (outing.squad == null) urlBuilder.outings(outing.squad) else urlBuilder.outingsUrl() + return ModelAndView(RedirectView(exitUrl)) + } + + @RequiresOutingPermission(permission = Permission.EDIT_OUTING) + @GetMapping("/outings/{id}/close") + fun closeOuting(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val outing = loggedInUserApi.getOuting(id) + + log.info("Closing outing: $outing") + outing.isClosed = true + loggedInUserApi.updateOuting(outing) + + return redirectToOuting(outing) + } + + @RequiresOutingPermission(permission = Permission.EDIT_OUTING) + @GetMapping("/outings/{id}/reopen") + fun reopenOuting(@PathVariable id: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val outing = loggedInUserApi.getOuting(id) + + log.info("Reopening outing: $outing") + outing.isClosed = false + loggedInUserApi.updateOuting(outing) + + return redirectToOuting(outing) + } + + @RequiresOutingPermission(permission = Permission.EDIT_OUTING) + @PostMapping("/outings/{id}/edit") + fun editOutingSubmit(@PathVariable id: String, + @Valid @ModelAttribute("outing") outingDetails: OutingDetails, result: BindingResult): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val outing = loggedInUserApi.getOuting(id) + if (result.hasErrors()) { + return renderEditOutingForm(outingDetails, outing) + } + try { + val updatedOuting = buildOutingFromOutingDetails(outingDetails, instanceSpecificApiClient.instance, loggedInUserApi) + updatedOuting.id = id + + loggedInUserApi.updateOuting(updatedOuting) + return redirectToOuting(updatedOuting) + + } catch (e: InvalidOutingException) { + result.addError(ObjectError("outing", e.message)) + return renderEditOutingForm(outingDetails, outing) + + } catch (e: Exception) { + log.error(e) + result.addError(ObjectError("outing", "Unknown exception")) + return renderEditOutingForm(outingDetails, outing) + } + + } + + @PostMapping("/availability/ajax") + fun updateAvailability( + @RequestParam(value = "outing", required = true) outingId: String, + @RequestParam(value = "availability", required = true) availability: String): ModelAndView { + val loggedInUserApi = squadlistApiFactory.createForToken(loggedInUserService.loggedInMembersToken) + + val outing = loggedInUserApi.getOuting(outingId) + + if (!outing.isClosed) { + val result = loggedInUserApi.setOutingAvailability(loggedInUserService.loggedInMember, outing, getAvailabilityOptionById(availability)) + return viewFactory.getViewForLoggedInUser("includes/availability").addObject("availability", result.availabilityOption) + } + + throw OutingClosedException() + } + + private fun redirectToOuting(updatedOuting: Outing): ModelAndView { + return ModelAndView(RedirectView(urlBuilder.outingUrl(updatedOuting))) + } + + private fun renderNewOutingForm(outingDetails: OutingDetails): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("newOuting") + mv.addObject("squads", instanceSpecificApiClient.squads) + val squad = preferedSquadService.resolveSquad(null) + mv.addObject("squad", squad) + mv.addObject("outingMonths", getOutingMonthsFor(squad)) + mv.addObject("outing", outingDetails) + return mv + } + + private fun renderEditOutingForm(outingDetails: OutingDetails, outing: Outing): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("editOuting") + mv.addObject("squads", instanceSpecificApiClient.squads) + mv.addObject("squad", outing.squad) + mv.addObject("outing", outingDetails) + mv.addObject("outingObject", outing) + mv.addObject("outingMonths", getOutingMonthsFor(outing.squad)) + mv.addObject("month", ISODateTimeFormat.yearMonth().print(outing.date.time)) // TODO push to date parser - local time + return mv + } + + @Throws(JsonParseException::class, JsonMappingException::class, HttpFetchException::class, IOException::class, UnknownAvailabilityOptionException::class) + private fun getAvailabilityOptionById(availabilityId: String): AvailabilityOption? { + return if (Strings.isNullOrEmpty(availabilityId)) { + null + } else instanceSpecificApiClient.getAvailabilityOption(availabilityId) + } + + private fun getOutingMonthsFor(squad: Squad?): Map { + return instanceSpecificApiClient.getOutingMonths(squad) + } + + private fun buildOutingFromOutingDetails(outingDetails: OutingDetails, instance: Instance, squadlistApi: SquadlistApi): Outing { + val date = outingDetails.toLocalTime().toDateTime(DateTimeZone.forID(instance.timeZone)).toDate() + val squad = if (outingDetails.squad != null) squadlistApi.getSquad(outingDetails.squad) else null // TODO validation + val notes = outingDetails.notes + return Outing(squad, date, notes) + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/ResetPasswordController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/ResetPasswordController.kt new file mode 100644 index 00000000..baa85c50 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/ResetPasswordController.kt @@ -0,0 +1,68 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Strings +import org.apache.log4j.Logger +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.servlet.ModelAndView +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.exceptions.UnknownMemberException +import uk.co.squadlist.web.urls.UrlBuilder + +@Controller +class ResetPasswordController(val api: InstanceSpecificApiClient, val urlBuilder: UrlBuilder) { + + private val log = Logger.getLogger(ResetPasswordController::class.java) + + @GetMapping("/reset-password") + fun resetPasswordPrompt(): ModelAndView { + return resetPasswordScreen().addObject("errors", false) + } + + @PostMapping("/reset-password") + fun resetPassword(@RequestParam(value = "username", required = false) username: String): ModelAndView { + if (Strings.isNullOrEmpty(username)) { + return resetPasswordScreen().addObject("errors", true) + } + + log.info("Resetting password for: $username") + try { + api.resetPassword(username) // TODO errors + log.info("Reset password call successful for: $username") + val mv = ModelAndView("resetPasswordSent") + mv.addObject("title", "Reset password") + mv.addObject("urlBuilder", urlBuilder) + return mv + + } catch (e: UnknownMemberException) { + return resetPasswordScreen().addObject("errors", true) + } + } + + @GetMapping("/reset-password/confirm") + fun confirmPasswordReset(@RequestParam token: String): ModelAndView { + try { + val newPassword = api.confirmResetPassword(token) + + val mv = ModelAndView("resetPasswordConfirm") + mv.addObject("newPassword", newPassword) + mv.addObject("urlBuilder", urlBuilder) + return mv + + } catch (e: Exception) { + return ModelAndView("resetPasswordInvalidToken"). + addObject("urlBuilder", urlBuilder) + } + + } + + private fun resetPasswordScreen(): ModelAndView { + val mv = ModelAndView("resetPassword") + mv.addObject("title", "Reset password") + mv.addObject("urlBuilder", urlBuilder) + return mv + } + +} \ No newline at end of file diff --git a/src/main/kotlin/uk/co/squadlist/web/controllers/SquadsController.kt b/src/main/kotlin/uk/co/squadlist/web/controllers/SquadsController.kt new file mode 100644 index 00000000..aa64a95b --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/controllers/SquadsController.kt @@ -0,0 +1,127 @@ +package uk.co.squadlist.web.controllers + +import com.google.common.base.Splitter +import com.google.common.collect.Sets +import org.apache.log4j.Logger +import org.springframework.stereotype.Controller +import org.springframework.validation.BindingResult +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.ModelAttribute +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.servlet.ModelAndView +import org.springframework.web.servlet.view.RedirectView +import uk.co.squadlist.web.annotations.RequiresPermission +import uk.co.squadlist.web.api.InstanceSpecificApiClient +import uk.co.squadlist.web.api.SquadlistApi +import uk.co.squadlist.web.api.SquadlistApiFactory +import uk.co.squadlist.web.context.InstanceConfig +import uk.co.squadlist.web.exceptions.InvalidSquadException +import uk.co.squadlist.web.model.Squad +import uk.co.squadlist.web.model.forms.SquadDetails +import uk.co.squadlist.web.services.Permission +import uk.co.squadlist.web.urls.UrlBuilder +import uk.co.squadlist.web.views.ViewFactory +import javax.validation.Valid + +@Controller +class SquadsController( + val instanceSpecificApiClient: InstanceSpecificApiClient, + val urlBuilder: UrlBuilder, + val viewFactory: ViewFactory, + val instanceConfig: InstanceConfig, + val squadlistApiFactory: SquadlistApiFactory) { + + private val log = Logger.getLogger(SquadsController::class.java) + + private val COMMA_SPLITTER = Splitter.on(",") + + private val squadlistApi: SquadlistApi = squadlistApiFactory.createClient() + + @GetMapping("/squad/new") + fun newSquad(@ModelAttribute("squadDetails") squadDetails: SquadDetails): ModelAndView { + return renderNewSquadForm(SquadDetails()) + } + + @PostMapping("/squad/new") + fun newSquadSubmit(@Valid @ModelAttribute("squadDetails") squadDetails: SquadDetails, result: BindingResult): ModelAndView { + if (result.hasErrors()) { + return renderNewSquadForm(squadDetails) + } + + try { + val instance = squadlistApi.getInstance(instanceConfig.instance) + squadlistApi.createSquad(instance, squadDetails.name) + + return ModelAndView(RedirectView(urlBuilder.adminUrl())) + + } catch (e: InvalidSquadException) { + log.info("Invalid squad") + result.rejectValue("name", null, "squad name is already in use") + return renderNewSquadForm(squadDetails) + } + + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @GetMapping("/squad/{id}/delete") + fun deletePrompt(@PathVariable id: String): ModelAndView { + val squad = squadlistApi.getSquad(id) + return viewFactory.getViewForLoggedInUser("deleteSquadPrompt").addObject("squad", squad).addObject("title", "Delete squad") + } + + @RequiresPermission(permission = Permission.VIEW_ADMIN_SCREEN) + @PostMapping("/squad/{id}/delete") + fun delete(@PathVariable id: String): ModelAndView { + val squad = squadlistApi.getSquad(id) + squadlistApi.deleteSquad(squad) + return ModelAndView(RedirectView(urlBuilder.adminUrl())) + } + + @GetMapping("/squad/{id}/edit") + fun editSquad(@PathVariable id: String): ModelAndView { + val squad = squadlistApi.getSquad(id) + + val squadDetails = SquadDetails() + squadDetails.name = squad.name + + return renderEditSquadForm(squad, squadDetails) + } + + @PostMapping("/squad/{id}/edit") + fun editSquadSubmit(@PathVariable id: String, @Valid @ModelAttribute("squadDetails") squadDetails: SquadDetails, result: BindingResult): ModelAndView { + val squad = squadlistApi.getSquad(id) + if (result.hasErrors()) { + return renderEditSquadForm(squad, squadDetails) + } + + squad.name = squadDetails.name + log.info("Updating squad: $squad") + squadlistApi.updateSquad(squad) + + val updatedSquadMembers = Sets.newHashSet(COMMA_SPLITTER.split(squadDetails.members).iterator()) + log.info("Setting squad members to " + updatedSquadMembers.size + " members: " + updatedSquadMembers) + squadlistApi.setSquadMembers(squad.id, updatedSquadMembers) + + return ModelAndView(RedirectView(urlBuilder.adminUrl())) + } + + private fun renderNewSquadForm(squadDetails: SquadDetails): ModelAndView { + return viewFactory.getViewForLoggedInUser("newSquad").addObject("squadDetails", squadDetails) + } + + private fun renderEditSquadForm(squad: Squad, squadDetails: SquadDetails): ModelAndView { + val mv = viewFactory.getViewForLoggedInUser("editSquad") + mv.addObject("squad", squad) + mv.addObject("squadDetails", squadDetails) + + val squadMembers = squadlistApi.getSquadMembers(squad.id) + mv.addObject("squadMembers", squadMembers) + + val availableMembers = instanceSpecificApiClient.members + availableMembers.removeAll(squadMembers) + mv.addObject("availableMembers", availableMembers) + return mv + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/urls/SeoLinkBuilder.kt b/src/main/kotlin/uk/co/squadlist/web/urls/SeoLinkBuilder.kt new file mode 100644 index 00000000..a0b70efb --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/urls/SeoLinkBuilder.kt @@ -0,0 +1,15 @@ +package uk.co.squadlist.web.urls + +import org.springframework.stereotype.Component + +@Component +class SeoLinkBuilder { + + fun makeSeoLinkFor(name: String): String { + var result = name.toLowerCase().trim { it <= ' ' }.replace("\\s".toRegex(), "-") + result = result.replace("[^\\-a-z0-9_]".toRegex(), "") + result = result.replace("--+".toRegex(), "-") + return result + } + +} diff --git a/src/main/kotlin/uk/co/squadlist/web/urls/UrlBuilder.kt b/src/main/kotlin/uk/co/squadlist/web/urls/UrlBuilder.kt new file mode 100644 index 00000000..09616b92 --- /dev/null +++ b/src/main/kotlin/uk/co/squadlist/web/urls/UrlBuilder.kt @@ -0,0 +1,286 @@ +package uk.co.squadlist.web.urls + +import com.google.common.base.Joiner +import com.google.common.collect.Lists +import org.apache.commons.codec.digest.DigestUtils +import org.apache.http.client.utils.URIBuilder +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component +import uk.co.squadlist.web.context.InstanceConfig +import uk.co.squadlist.web.localisation.GoverningBody +import uk.co.squadlist.web.model.* +import java.net.URISyntaxException + +@Component +class UrlBuilder (@param:Value("\${baseUrl}") private val baseUrl: String, + val instanceConfig: InstanceConfig, + val seoLinkBuilder: SeoLinkBuilder, + @param:Value("\${apiUrl}") private val apiUrl: String, + @param:Value("\${profilePicturesUrl}") private val profilePicturesUrl: String) { + + val linkFacebookCallbackUrl: String + get() = applicationUrl("/social/facebook/link/callback") + + fun applicationUrl(uri: String): String { + return getBaseUrl() + uri + } + + fun loginUrl(): String { + return applicationUrl("/login") + } + + fun resetPassword(): String { + return applicationUrl("/reset-password") + } + + fun staticUrl(uri: String): String { + return applicationUrl("/assets/") + uri + } + + fun boatUrl(boat: Boat): String { + return applicationUrl("/boats/" + boat.id) + } + + fun memberUrl(member: Member): String { + return memberUrl(member.id) + } + + fun memberResetPasswordUrl(member: Member): String { + return memberUrl(member.id) + "/reset" + } + + fun memberUrl(memberId: String): String { + return applicationUrl("/member/$memberId") + } + + fun makeActive(member: Member): String { + return memberUrl(member.id) + "/make-active" + } + + fun makeInactive(member: Member): String { + return memberUrl(member.id) + "/make-inactive" + } + + fun delete(member: Member): String { + return memberUrl(member.id) + "/delete" + } + + fun delete(squad: Squad): String { + return applicationUrl("/squad/" + squad.id + "/delete") + } + + fun newMemberUrl(): String { + return applicationUrl("/member/new") + } + + fun newSquadUrl(): String { + return applicationUrl("/squad/new") + } + + fun editSquadUrl(squad: Squad): String { + return applicationUrl("/squad/" + squad.id + "/edit") + } + + fun adminUrl(): String { + return applicationUrl("/admin") + } + + fun outingsUrl(): String { + return applicationUrl("/outings") + } + + fun outingUrl(outing: Outing): String { + return outingsUrl() + "/" + outing.id + } + + fun outingAvailabilityCsv(outing: Outing): String { + return outingUrl(outing) + ".csv" + } + + fun outingCloseUrl(outing: Outing): String { + return outingUrl(outing) + "/close" + } + + fun deleteOuting(outing: Outing): String { + return outingUrl(outing) + "/delete" + } + + fun outingReopenUrl(outing: Outing): String { + return outingUrl(outing) + "/reopen" + } + + fun outingEditUrl(outing: Outing): String { + return outingUrl(outing) + "/edit" + } + + fun entryDetails(squad: Squad): String { + return applicationUrl("/entry-details/" + squad.id) + } + + fun outings(squad: Squad): String { + return applicationUrl("/outings?squad=" + squad.id) + } + + fun outings(squad: Squad, month: String): String { + return outings(squad) + "&month=" + month + } + + fun availability(squad: Squad): String { + return applicationUrl("/availability/" + squad.id) + } + + fun availability(squad: Squad, month: String): String { + return availability(squad) + "?month=" + month + } + + fun editInstanceSettings(): String { + return adminUrl() + "/instance" + } + + fun editMemberUrl(member: Member): String { + return memberUrl(member) + "/edit" + } + + fun editMemberUrl(memberId: String): String { + return memberUrl(memberId) + "/edit" + } + + fun entryDetailsCsv(squad: Squad): String { + return applicationUrl("/entry-details/" + squad.id + ".csv") + } + + fun entryDetailsCsv(members: List): String { + try { + val url = URIBuilder(applicationUrl("/entry-details/selected.csv")) + + val memberIds = Lists.newArrayList() + for (member in members) { + memberIds.add(member.id) + } + url.addParameter("members", Joiner.on(",").join(memberIds)) + + return url.toString() + + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + + } + + fun changePassword(): String { + return applicationUrl("/change-password") + } + + fun socialMediaAccounts(): String { + return applicationUrl("/social") + } + + fun linkFacebookUrl(): String { + return applicationUrl("/social/facebook/link") + } + + fun removeFacebookUrl(): String { + return applicationUrl("/social/facebook/remove") + } + + fun facebookSigninCallbackUrl(): String { + return applicationUrl("/social/facebook/signin/callback") + } + + fun facebookSignin(): String { + return applicationUrl("/social/facebook/signin") + } + + fun getBaseUrl(): String { + return baseUrl.replace("INSTANCE", instanceConfig.vhost) + } + + fun contactsUrl(): String { + return applicationUrl("/contacts") + } + + fun contactsUrl(prefferredSquad: Squad): String { + return appendSquad(prefferredSquad, contactsUrl()) + } + + @Throws(URISyntaxException::class) + fun outingsUrl(prefferredSquad: Squad?): String { + val url = URIBuilder(outingsUrl()) + if (prefferredSquad != null) { + url.addParameter("squad", prefferredSquad.id) + } + return url.build().toString() + } + + @Throws(URISyntaxException::class) + fun outingsRss(userid: String, instance: Instance): String { + val url = URIBuilder(applicationUrl("/rss")) + url.addParameter("user", userid) + url.addParameter("key", generateFeedKeyFor(userid, instance)) + return url.build().toString() + } + + @Throws(URISyntaxException::class) + fun outingsIcal(userid: String, instance: Instance): String { + val webcalBaseUrl = getBaseUrl().replace("^https?://".toRegex(), "webcal://") + val url = URIBuilder("$webcalBaseUrl/ical") + url.addParameter("user", userid) + url.addParameter("key", generateFeedKeyFor(userid, instance)) + return url.build().toString() + } + + fun availabilityUrl(prefferredSquad: Squad): String { + return appendSquad(prefferredSquad, availabilityUrl()) + } + + fun governingBody(governingBody: GoverningBody): String { + return applicationUrl("/governing-body/" + seoLinkBuilder.makeSeoLinkFor(governingBody.name)) + } + + fun staticImage(filename: String): String { + return "$apiUrl/static/$filename.jpg" + } + + fun profileImage(filename: String): String { + return "$profilePicturesUrl/$filename.jpg" // TODO ideally all of this would be provided by the API + } + + fun newAvailabilityOptionUrl(): String { + return applicationUrl("/availability-option/new") + } + + fun deleteAvailabilityOptionUrl(availabilityOption: AvailabilityOption): String { + return applicationUrl("/availability-option/" + availabilityOption.id + "/delete") + } + + fun editAvailabilityOptionUrl(availabilityOption: AvailabilityOption): String { + return applicationUrl("/availability-option/" + availabilityOption.id + "/edit") + } + + fun editAdmins(): String { + return adminUrl() + "/admins" + } + + fun adminExportMembersAsCSV(): String { + return adminUrl() + "/export/members.csv" + } + + private fun availabilityUrl(): String { + return applicationUrl("/availability") + } + + private fun appendSquad(prefferredSquad: Squad?, baseUrl: String): String { + return if (prefferredSquad != null) baseUrl + "/" + prefferredSquad.id else baseUrl + } + + private fun generateFeedKeyFor(userid: String, instance: Instance): String { + return DigestUtils.md5Hex(instance.id + userid) + } + + fun mailto(emails: List): String? { + return if (emails.isEmpty()) { + null + } else "mailto:" + Joiner.on(",").join(emails) + } + +} diff --git a/src/main/resources/changePassword.vm b/src/main/resources/changePassword.vm deleted file mode 100644 index 6024380b..00000000 --- a/src/main/resources/changePassword.vm +++ /dev/null @@ -1,60 +0,0 @@ -#parse('includes/head.vm') -#navTabs('Outings') - -
- -
-
-

$text.text('change.password')

-
-
-
- -
- -
- - #springBind("changePassword") - #foreach($error in $status.errorMessages) -
$error
- #end - -
- - #springBind("changePassword.currentPassword") -
- -
- - #foreach($error in $status.errorMessages) -
$error
- #end -
-
- - #springBind("changePassword.newPassword") -
- -
- - #foreach($error in $status.errorMessages) -
$error
- #end -
-
- -
-
-
-
- - #cancel($urlBuilder.memberUrl($member)) -
-
-
-
-
-
-
- -#parse('includes/footer.vm') diff --git a/src/main/resources/contacts.vm b/src/main/resources/contacts.vm deleted file mode 100644 index 0137a854..00000000 --- a/src/main/resources/contacts.vm +++ /dev/null @@ -1,67 +0,0 @@ -#parse('includes/head.vm') -#navTabs('Contacts') - -
- - #if(!$squads.isEmpty()) - -
-
-

$text.text('contacts')

-
-
-
- #squadSelect('/contacts/', 'Show contacts') -
-
-
-
- - #if($members.isEmpty()) -

$text.text('no.members.assigned.to.squad')

- #else - - #if($emails) -

$text.text('email.members')

- #end - - - - - - - - - - - #foreach( $member in $members ) - - - - - - #end -
$text.text('email')$text.text('phone')
- #parse('includes/member.vm') - #if($member.role && $member.role != 'Rower') - ($!member.role) - #end - - #if($member.emailAddress) - $member.emailAddress - #end - - #if($member.contactNumber) - $member.contactNumber - #end -
- #end - - #else -

$text.text('contacts')

-
-

There are currently no squads setup.

- #end - -
-
\ No newline at end of file diff --git a/src/main/resources/entryDetails.vm b/src/main/resources/entryDetails.vm deleted file mode 100644 index e7e3710c..00000000 --- a/src/main/resources/entryDetails.vm +++ /dev/null @@ -1,122 +0,0 @@ -#parse('includes/head.vm') -#navTabs('Entry details') - -
- - #if(!$squads.isEmpty()) - -
-
-

$text.text('entry.details')

-
-
-
- #squadSelect('/entrydetails/', 'Show entry details') -
-
-
-
- - #if($members.isEmpty()) -

$text.text('no.members.assigned.to.squad')

- #else - -
-
- - - - - - - - - - - - - - - - - #foreach( $member in $members ) - - - - - - - - - - - #end - - -
$text.text('effective.age')$text.text('weight')$text.text('rowing')$text.text('sculling')$text.text('registration')
#parse('includes/member.vm') - #if($member.dateOfBirth) - $dateFormatter.dayMonthYear($member.dateOfBirth) -
- #set($ageGrade = "") - #set($ageGrade = $governingBody.getAgeGrade($governingBody.getEffectiveAge($member.dateOfBirth))) - $governingBody.getEffectiveAge($member.dateOfBirth) - #if($ageGrade)
$ageGrade #end - #end -
$!member.weight$!member.rowingPoints -
#if($member.rowingPoints) $!governingBody.getRowingStatus($member.rowingPoints) #end -
$!member.scullingPoints - #if($member.scullingPoints)
$!governingBody.getScullingStatus($member.scullingPoints) #end -
- #set($registrationNumberProblems = '') - #set($registrationNumberProblems = $governingBody.checkRegistrationNumber($member.registrationNumber)) - #if($registrationNumberProblems && $registrationNumberProblems != '') - $!member.registrationNumber -
- $registrationNumberProblems -
- #end -
- #end - - #else -

$text.text('entry.details')

-
-

There are currently no squads setup.

- #end - -
-
-

$text.text('governing.body'): $governingBody.name

-

- -
- -
-
- - - -#parse('includes/footer.vm') \ No newline at end of file diff --git a/src/main/resources/includes/footer.vm b/src/main/resources/includes/footer.vm deleted file mode 100644 index 09bc863f..00000000 --- a/src/main/resources/includes/footer.vm +++ /dev/null @@ -1,4 +0,0 @@ -#parse('includes/analytics.vm') - - - diff --git a/src/main/resources/includes/head.vm b/src/main/resources/includes/head.vm deleted file mode 100644 index edb0ca4f..00000000 --- a/src/main/resources/includes/head.vm +++ /dev/null @@ -1,23 +0,0 @@ -#parse('macros.vm') - - - - - - - - - - - - - - - Squadlist - $!title - - #if($rssUrl) - - #end - - - diff --git a/src/main/resources/includes/member.vm b/src/main/resources/includes/member.vm deleted file mode 100644 index 7c7183c8..00000000 --- a/src/main/resources/includes/member.vm +++ /dev/null @@ -1,8 +0,0 @@ -#if($permissionsHelper.hasMemberPermission($member, 'EDIT_MEMBER_DETAILS')) - - $member.displayName - -#else - $member.displayName -#end diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 00000000..2bdf460f --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=info, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n diff --git a/src/main/resources/macros.vm b/src/main/resources/macros.vm index bedbbfec..1932ca19 100644 --- a/src/main/resources/macros.vm +++ b/src/main/resources/macros.vm @@ -26,58 +26,6 @@ #end -#macro(navTabs $selectedTab) - -#end - #macro(inputRow $field $label) #springBind($field)
@@ -144,23 +92,4 @@
-#end - -#macro(squadSelect $action $label) - #if ($squads) -
- - -
- #end -#end - -#macro(cancel $action) - #end \ No newline at end of file diff --git a/src/main/resources/en.properties b/src/main/resources/messages.properties similarity index 97% rename from src/main/resources/en.properties rename to src/main/resources/messages.properties index 89e17ad6..b30981d3 100644 --- a/src/main/resources/en.properties +++ b/src/main/resources/messages.properties @@ -4,7 +4,7 @@ forgotten.your.password=Forgotten your password? login=Login incorrect.login=Incorrect username or password login.with.linked.facebook.account=Or sign in using a previously linked Facebook account -require.access=If you are a {} member who requires access to this copy of Squadlist please contact your coach. +require.access=If you are a {0} member who requires access to this copy of Squadlist please contact your coach. my.outings=My outings my.details=My details diff --git a/src/main/resources/resetPassword.vm b/src/main/resources/resetPassword.vm deleted file mode 100644 index 79ee092a..00000000 --- a/src/main/resources/resetPassword.vm +++ /dev/null @@ -1,51 +0,0 @@ -#parse('includes/head.vm') - -
- -
-
-

Squadlist - $instance.name

-
-
-
- -
-
-

$text.text('resetting.your.password')

-

$text.text('reset.password.instructions')

-
-
- -
-
-
-
- -
- -
-
- -
-
-
-
- #if($errors) -
$text.text('unknown.username.or.email.address')
- #end -
-
- -
-
- - #cancel($urlBuilder.loginUrl()) -
-
-
-
-
-
-
- -#parse('includes/footer.vm') \ No newline at end of file diff --git a/src/main/resources/resetPasswordConfirm.vm b/src/main/resources/resetPasswordConfirm.vm deleted file mode 100644 index dcd23eac..00000000 --- a/src/main/resources/resetPasswordConfirm.vm +++ /dev/null @@ -1,28 +0,0 @@ -#parse('includes/head.vm') - -
- -
-
-

Squadlist - $instance.name

-
-
-
- -
-
-

$text.text('resetting.your.password')

- -

$text.text('your.new.password.is') $newPassword

-
-
- - -
-
- -#parse('includes/footer.vm') diff --git a/src/main/resources/templates/500.ftl b/src/main/resources/templates/500.ftl new file mode 100644 index 00000000..17416ae3 --- /dev/null +++ b/src/main/resources/templates/500.ftl @@ -0,0 +1 @@ +ERROR \ No newline at end of file diff --git a/src/main/resources/templates/TODO.vm b/src/main/resources/templates/TODO.vm deleted file mode 100644 index e2efa2b9..00000000 --- a/src/main/resources/templates/TODO.vm +++ /dev/null @@ -1 +0,0 @@ -Disable template path check \ No newline at end of file diff --git a/src/main/resources/templates/changePassword.ftl b/src/main/resources/templates/changePassword.ftl new file mode 100644 index 00000000..e74b6d1c --- /dev/null +++ b/src/main/resources/templates/changePassword.ftl @@ -0,0 +1,58 @@ +<#include 'includes/head.ftl'> +<@navTabs '' /> + +
+ +
+
+

<@spring.message 'change.password' />

+
+
+
+ +
+
+ <@spring.bind "changePassword" /> + + <#list spring.status.errorMessages as error> +
${error}
+ + +
+ <@spring.bind "changePassword.currentPassword" /> +
+ +
+ + <#list spring.status.errorMessages as error> +
${error}
+ +
+
+ + <@spring.bind "changePassword.newPassword" /> +
+ +
+ + <#list spring.status.errorMessages as error> +
${error}
+ +
+
+ +
+
+
+
+ + <@cancel urlBuilder.memberUrl(member) /> +
+
+
+
+
+
+
+ +<#include 'includes/footer.ftl'> \ No newline at end of file diff --git a/src/main/resources/templates/contacts.ftl b/src/main/resources/templates/contacts.ftl new file mode 100644 index 00000000..3cfaca44 --- /dev/null +++ b/src/main/resources/templates/contacts.ftl @@ -0,0 +1,69 @@ +<#include 'includes/head.ftl'> +<@navTabs 'Contacts' /> + +
+ + <#if squads?size != 0> +
+
+

<@spring.message 'contacts' />

+
+
+
+ <@squadSelect '/contacts/' 'Show contacts' /> +
+
+
+
+ + <#if members?size== 0> +

<@spring.message 'no.members.assigned.to.squad' />

+ <#else> + + <#if emails?? > +

<@spring.message 'email.members' />

+ + + + + + + + + + + + <#list members as member> + + + + + + +
<@spring.message 'email' /><@spring.message 'phone' />
+ <#include 'includes/member.ftl'> + <#if member.role?? && member.role != 'Rower' > + (${member.role}) + + + <#if member.emailAddress?? > + ${member.emailAddress} + + + <#if member.contactNumber?? > + ${member.contactNumber} + +
+ + + <#else> +

<@spring.message 'contacts' />

+
+

There are currently no squads setup.

+ + +
+
+ +<#include 'includes/footer.ftl'> + diff --git a/src/main/resources/data.vm b/src/main/resources/templates/data.ftl similarity index 100% rename from src/main/resources/data.vm rename to src/main/resources/templates/data.ftl diff --git a/src/main/resources/templates/entryDetails.ftl b/src/main/resources/templates/entryDetails.ftl new file mode 100644 index 00000000..be5f44bb --- /dev/null +++ b/src/main/resources/templates/entryDetails.ftl @@ -0,0 +1,121 @@ +<#include 'includes/head.ftl'> +<@navTabs 'Entry details' /> + +
+ + <#if squads?size != 0> +
+
+

<@spring.message 'entry.details' />

+
+
+
+ <@squadSelect '/entry-details/' 'Show entry details' /> +
+
+
+
+ + <#if members?size == 0> +

<@spring.message 'no.members.assigned.to.squad' />

+ + <#else> +
+ + + + + + + + + + + + + + <#list members as member> + + + + + + + + + + + +
<@spring.message 'effective.age' /><@spring.message 'weight' /><@spring.message 'rowing' /><@spring.message 'sculling' /><@spring.message 'registration' />
<#include 'includes/member.ftl'> + <#if member.dateOfBirth??> + ${dateFormatter.dayMonthYear(member.dateOfBirth)} +
+ <#assign effectiveAge = governingBody.getEffectiveAge(member.dateOfBirth) > + <#assign ageGrade = governingBody.getAgeGrade(effectiveAge)!"" > + title="${ageGrade}" >${effectiveAge} + <#if ageGrade?? >
${ageGrade} + +
${member.weight!} + <#if member.rowingPoints??> + <#assign rowingStatus = governingBody.getRowingStatus(member.rowingPoints) > + ${member.rowingPoints!} +
${rowingStatus!} + +
+ <#if member.scullingPoints??> + <#assign scullingStatus = governingBody.getScullingStatus(member.scullingPoints) > + ${member.scullingPoints!} +
${scullingStatus!} + +
+ <#if member.registrationNumber??> + ${member.registrationNumber} + <#assign registrationNumberProblems = governingBody.checkRegistrationNumber(member.registrationNumber)!""> + <#if registrationNumberProblems != "" > +
+ ${registrationNumberProblems} +
+ + +
+ + + <#else> +

<@spring.message 'entry.details' />

+
+

There are currently no squads setup.

+ + +
+
+

<@spring.message 'governing.body' />: ${governingBody.name}

+

+ +
+
+
+ + + +<#include 'includes/footer.ftl'> \ No newline at end of file diff --git a/src/main/resources/entryDetailsAjax.vm b/src/main/resources/templates/entryDetailsAjax.ftl similarity index 100% rename from src/main/resources/entryDetailsAjax.vm rename to src/main/resources/templates/entryDetailsAjax.ftl diff --git a/src/main/resources/governingBody.vm b/src/main/resources/templates/governingBody.ftl similarity index 65% rename from src/main/resources/governingBody.vm rename to src/main/resources/templates/governingBody.ftl index 4715232f..aa760718 100644 --- a/src/main/resources/governingBody.vm +++ b/src/main/resources/templates/governingBody.ftl @@ -1,60 +1,60 @@ -#parse('includes/head.vm') -#navTabs('Admin') +<#include 'includes/head.ftl'> +<@navTabs 'Governing body' />
-

$title

-
+

${title!}

+

- +
-
-

$text.text('localised.for.governing.body', $governingBody.name) (link)

+
+

$text.text('localised.for.governing.body', $governingBody.name) (link)

- +
-
+
Statuses
#table($statuses) - +
Boat sizes
- $boatSizes + $boatSizes
- +
-
Age grades
+
Age grades
#table($ageGrades) - +
$text.text('effective.age')
-

$governingBody.effectiveAgeDescription

+

$governingBody.effectiveAgeDescription

- +
-

Please contact us if you are interested in developing a localisation for a different governing body.

+

Please contact us if you are interested in developing a localisation for a different governing body.

- +

-#macro(table $values) +#macro(table $values) - #foreach($value in $values.keySet()) + #foreach($value in $values.keySet()) #end
$value #if($values.get($value)) $values.get($value) - #end + #end
#end -#parse('includes/footer.vm') \ No newline at end of file +<#include 'includes/footer.ftl'> \ No newline at end of file diff --git a/src/main/resources/includes/analytics.vm b/src/main/resources/templates/includes/analytics.ftl similarity index 71% rename from src/main/resources/includes/analytics.vm rename to src/main/resources/templates/includes/analytics.ftl index f5d6baad..736b569a 100644 --- a/src/main/resources/includes/analytics.vm +++ b/src/main/resources/templates/includes/analytics.ftl @@ -1,12 +1,13 @@ -#if($googleAnalyticsAccount) +<#if googleAnalyticsAccount??> -#end + + diff --git a/src/main/resources/templates/includes/footer.ftl b/src/main/resources/templates/includes/footer.ftl new file mode 100644 index 00000000..205c9616 --- /dev/null +++ b/src/main/resources/templates/includes/footer.ftl @@ -0,0 +1,3 @@ + <#include 'analytics.ftl'> + + diff --git a/src/main/resources/templates/includes/head.ftl b/src/main/resources/templates/includes/head.ftl new file mode 100644 index 00000000..4e51a2f2 --- /dev/null +++ b/src/main/resources/templates/includes/head.ftl @@ -0,0 +1,94 @@ +<#import "/spring.ftl" as spring/> + + + + + + + + + + + + + + + Squadlist - ${title!} + + <#if rssUrl??> + + + + + +<#macro cancel to> + + + +<#macro navTabs selectedTab> + + + +<#macro squadSelect action label> + <#if squads??> +
+ + +
+ + + + diff --git a/src/main/resources/templates/includes/member.ftl b/src/main/resources/templates/includes/member.ftl new file mode 100644 index 00000000..715abd3d --- /dev/null +++ b/src/main/resources/templates/includes/member.ftl @@ -0,0 +1,8 @@ +<#if permissionsHelper.hasMemberPermission(member, 'EDIT_MEMBER_DETAILS') > + + ${member.displayName} + +<#else> + ${member.displayName} + diff --git a/src/main/resources/templates/login.ftl b/src/main/resources/templates/login.ftl new file mode 100644 index 00000000..70ca87f1 --- /dev/null +++ b/src/main/resources/templates/login.ftl @@ -0,0 +1,100 @@ +<#include 'includes/head.ftl'> + +
+ +
+
+

Squadlist - ${instance.name!}

+
+
+
+
+
+
+
+ +
+ " value="${username!}"> +
+
+ +
+ + +
+ +
+
+
+ + +
+
+ +
+
+
+
+ <#if errors> +
<@spring.message "incorrect.login"/>
+ +
+
+ +
+
+
+
+ " class="btn btn-primary" /> +
+
+ +
+
+
+ + + +
+
+
+

+
+

<@spring.messageText "require.access", ["${instance.name}"] />

+

+
+

+
+ +
+
+ +
+
+ <#include 'data.ftl'> +
+
+
+
+ +
+ +
+ +<#include 'includes/footer.ftl'> \ No newline at end of file diff --git a/src/main/resources/myOutings.vm b/src/main/resources/templates/myOutings.ftl similarity index 60% rename from src/main/resources/myOutings.vm rename to src/main/resources/templates/myOutings.ftl index 33a3c557..bb4bef65 100644 --- a/src/main/resources/myOutings.vm +++ b/src/main/resources/templates/myOutings.ftl @@ -1,5 +1,5 @@ -#parse('includes/head.vm') -#navTabs('My outings') +<#include 'includes/head.ftl'> +<@navTabs 'My outings' /> @@ -7,54 +7,54 @@
-

$title

+

${title!}

- iCal + iCal

- - #if($member.squads.isEmpty()) -
$text.text('not.assigned.to.any.squads')
-

$text.text('no.outing.because.not.assigned.to.any.squads')

+ + <#if member.squads?size == 0> +
<@spring.message 'not.assigned.to.any.squads' />
+

<@spring.message 'no.outing.because.not.assigned.to.any.squads' />

- #else + <#else> - #if($outings.isEmpty()) + <#if outings?size == 0>
-
-
$text.text('no.current.outings')
-

$text.text('no.current.outings.for.your.squads')

+
+
<@spring.message 'no.current.outings' />
+

<@spring.message 'no.current.outings.for.your.squads' />

- - #else + + <#else>
-
+
- +

$squadNamesHelper.list($member.squads) outings.

- + - #foreach($outingAvailability in $outings) + <#list outings as outingAvailability> - - + - - #end +
+ #set($outing = $outingAvailability.outing) - #parse('includes/outing.vm') - + #parse('includes/outing.vm') + #if($outing.closed) #parse('includes/closedOuting.vm') - - #else + + #else #end - #if ($outingAvailability.availabilityOption) + + #if ($outingAvailability.availabilityOption) #set($availability = $outingAvailability.availabilityOption) - #parse('includes/availability.vm') + #parse('includes/availability.vm') - + #else TBA - #end + #end
-
+
- #end - #end + +

@@ -88,35 +88,35 @@ $('.availabilityDropdown').change(function() { outingId = $(this).attr('id'); newAvailability = $(this).val(); - + $("#currentAvailability" + outingId).html($('#spinner').html()) - - updateUrl = "$urlBuilder.applicationUrl('/availability/ajax')"; + + updateUrl = "${urlBuilder.applicationUrl('/availability/ajax')}"; postData = { outing: outingId, availability: newAvailability }; redrawPendingOutingBadge = function(data) {$("#pendingOutings").html(data)}; - pendingOutingsCountUrl = '$urlBuilder.applicationUrl('/myoutings/ajax')'; - + pendingOutingsCountUrl = '${urlBuilder.applicationUrl('/myoutings/ajax')}'; + redrawCurrentOutingAvailabilityAndPendingOutingBadge = function(data) { - $("#currentAvailability" + outingId).html(data); + $("#currentAvailability" + outingId).html(data); $.post(pendingOutingsCountUrl, {}, redrawPendingOutingBadge); }; - + $.ajax( { url: updateUrl, type: 'POST', data: postData, - dataType: 'text', - success: function(data, status, xhr) { + dataType: 'text', + success: function(data, status, xhr) { redrawCurrentOutingAvailabilityAndPendingOutingBadge(data); }, error: function(xhr, status, error) { - $("#currentAvailability" + outingId).html("Error: could not be updated"); + $("#currentAvailability" + outingId).html("Error: could not be updated"); } } ); - + }); -#parse('includes/footer.vm') \ No newline at end of file +<#include 'includes/footer.ftl'> diff --git a/src/main/resources/templates/resetPassword.ftl b/src/main/resources/templates/resetPassword.ftl new file mode 100644 index 00000000..2640248e --- /dev/null +++ b/src/main/resources/templates/resetPassword.ftl @@ -0,0 +1,51 @@ +<#include 'includes/head.ftl'> + +
+ +
+
+

Squadlist - ${instance.name}

+
+
+
+ +
+
+

<@spring.message 'resetting.your.password' />

+

<@spring.message 'reset.password.instructions' />

+
+
+ +
+
+
+
+ +
+ +
+
+ +
+
+
+
+ <#if errors> +
<@spring.message 'unknown.username.or.email.address' />
+ +
+
+ +
+
+ + <@cancel urlBuilder.loginUrl() /> +
+
+
+
+
+
+
+ +<#include 'includes/footer.ftl'> \ No newline at end of file diff --git a/src/main/resources/templates/resetPasswordConfirm.ftl b/src/main/resources/templates/resetPasswordConfirm.ftl new file mode 100644 index 00000000..7e754351 --- /dev/null +++ b/src/main/resources/templates/resetPasswordConfirm.ftl @@ -0,0 +1,27 @@ +<#include 'includes/head.ftl'> + +
+ +
+
+

Squadlist - ${instance.name}

+
+
+
+ +
+
+

<@spring.message 'resetting.your.password' />

+

<@spring.message 'your.new.password.is' />${newPassword}

+
+
+ + +
+
+ +<#include 'includes/footer.ftl'> \ No newline at end of file diff --git a/src/main/resources/resetPasswordInvalidToken.vm b/src/main/resources/templates/resetPasswordInvalidToken.ftl similarity index 57% rename from src/main/resources/resetPasswordInvalidToken.vm rename to src/main/resources/templates/resetPasswordInvalidToken.ftl index cc8b8d30..800686c9 100644 --- a/src/main/resources/resetPasswordInvalidToken.vm +++ b/src/main/resources/templates/resetPasswordInvalidToken.ftl @@ -1,10 +1,10 @@ -#parse('includes/head.vm') +<#include 'includes/head.ftl'>
-

Squadlist - $instance.name

+

Squadlist - ${instance.name}


@@ -17,10 +17,10 @@
-#parse('includes/footer.vm') \ No newline at end of file +<#include 'includes/footer.ftl'> \ No newline at end of file diff --git a/src/main/resources/resetPasswordSent.vm b/src/main/resources/templates/resetPasswordSent.ftl similarity index 62% rename from src/main/resources/resetPasswordSent.vm rename to src/main/resources/templates/resetPasswordSent.ftl index d82385aa..6b5a0b66 100644 --- a/src/main/resources/resetPasswordSent.vm +++ b/src/main/resources/templates/resetPasswordSent.ftl @@ -1,17 +1,17 @@ -#parse('includes/head.vm') +<#include 'includes/head.ftl'>
-

Squadlist - $instance.name

+

Squadlist - ${instance.name}


-

Reseting your password

+

<@spring.message 'resetting.your.password' />

An email containing password reset instructions has been sent to your email address.

It should arrive within a few minutes. @@ -21,10 +21,10 @@


-#parse('includes/footer.vm') \ No newline at end of file +<#include 'includes/footer.ftl'> \ No newline at end of file diff --git a/src/test/java/uk/co/squadlist/web/localisation/BritishRowingTest.java b/src/test/java/uk/co/squadlist/web/localisation/BritishRowingTest.java index d7dfd1de..dc307973 100644 --- a/src/test/java/uk/co/squadlist/web/localisation/BritishRowingTest.java +++ b/src/test/java/uk/co/squadlist/web/localisation/BritishRowingTest.java @@ -45,9 +45,9 @@ public void shouldReturnNullProblemsForNonSetRegistrationNumber() throws Excepti @Test public void effectiveAgeForMastersIsTheAgeTheyWouldReachBy31DecOfTheCurrentYear() throws Exception { - assertEquals(43, britishRowing.getEffectiveAge(new DateTime(1975, 12, 13, 0, 0, 0, 0).toDate())); - assertEquals(43, britishRowing.getEffectiveAge(new DateTime(1975, 1, 1, 0, 0, 0, 0).toDate())); - assertEquals(43, britishRowing.getEffectiveAge(new DateTime(1975, 12, 31, 0, 0, 0, 0).toDate())); + assertEquals(44, britishRowing.getEffectiveAge(new DateTime(1975, 12, 13, 0, 0, 0, 0).toDate())); + assertEquals(44, britishRowing.getEffectiveAge(new DateTime(1975, 1, 1, 0, 0, 0, 0).toDate())); + assertEquals(44, britishRowing.getEffectiveAge(new DateTime(1975, 12, 31, 0, 0, 0, 0).toDate())); } @Test diff --git a/src/test/java/uk/co/squadlist/web/localisation/RowingIrelandTest.java b/src/test/java/uk/co/squadlist/web/localisation/RowingIrelandTest.java index 863084d0..985209ac 100644 --- a/src/test/java/uk/co/squadlist/web/localisation/RowingIrelandTest.java +++ b/src/test/java/uk/co/squadlist/web/localisation/RowingIrelandTest.java @@ -67,9 +67,9 @@ public void crewPointsCannotBeInferredIfSomeCrewMembersHaveNotProvidedPointsInfo @Test public void effectiveAgeForMastersIsTheAgeTheyWouldReachBy31DecOfTheCurrentYear() throws Exception { - assertEquals(43, rowingIreland.getEffectiveAge(new DateTime(1975, 12, 13, 0, 0, 0, 0).toDate())); - assertEquals(43, rowingIreland.getEffectiveAge(new DateTime(1975, 1, 1, 0, 0, 0, 0).toDate())); - assertEquals(43, rowingIreland.getEffectiveAge(new DateTime(1975, 12, 31, 0, 0, 0, 0).toDate())); + assertEquals(44, rowingIreland.getEffectiveAge(new DateTime(1975, 12, 13, 0, 0, 0, 0).toDate())); + assertEquals(44, rowingIreland.getEffectiveAge(new DateTime(1975, 1, 1, 0, 0, 0, 0).toDate())); + assertEquals(44, rowingIreland.getEffectiveAge(new DateTime(1975, 12, 31, 0, 0, 0, 0).toDate())); } @Test diff --git a/src/test/java/uk/co/squadlist/web/urls/UrlBuilderTest.java b/src/test/java/uk/co/squadlist/web/urls/UrlBuilderTest.java index eb7fde81..dbcffa46 100644 --- a/src/test/java/uk/co/squadlist/web/urls/UrlBuilderTest.java +++ b/src/test/java/uk/co/squadlist/web/urls/UrlBuilderTest.java @@ -9,7 +9,6 @@ import uk.co.squadlist.web.localisation.BritishRowing; import uk.co.squadlist.web.model.Instance; import uk.co.squadlist.web.model.Subscription; -import uk.co.squadlist.web.model.Tariff; import java.util.List; @@ -18,56 +17,56 @@ public class UrlBuilderTest { - @Mock - private InstanceConfig instanceConfig; - private SeoLinkBuilder seoLinkBuilder; + @Mock + private InstanceConfig instanceConfig; + private SeoLinkBuilder seoLinkBuilder; - private Instance instance; + private Instance instance; - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - when(instanceConfig.getVhost()).thenReturn("twrc"); - seoLinkBuilder = new SeoLinkBuilder(); + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(instanceConfig.getVhost()).thenReturn("twrc"); + seoLinkBuilder = new SeoLinkBuilder(); - instance = new Instance("aninstance", "An instance", null, true, Lists.newArrayList(), "british-rowing"); - } - - @Test - public void canInsertInstanceIntoBaseUrl() throws Exception { - final UrlBuilder urlBuilder = new UrlBuilder("https://INSTANCE.squadlist.co.uk", instanceConfig, seoLinkBuilder, null, null); - assertEquals("https://twrc.squadlist.co.uk/meh", urlBuilder.applicationUrl("/meh")); - } + instance = new Instance("aninstance", "An instance", null, true, Lists.newArrayList(), "british-rowing"); + } - @Test - public void canComposeWebcalFeedUrlFromBaseUrl() throws Exception { - final UrlBuilder urlBuilder = new UrlBuilder("http://localhost", instanceConfig, seoLinkBuilder, null, null); - assertEquals("webcal://localhost/ical?user=a-user&key=9329bb2beff6556bf2698163732bb9a2", urlBuilder.outingsIcal("a-user", instance)); - } + @Test + public void canInsertInstanceIntoBaseUrl() throws Exception { + final UrlBuilder urlBuilder = new UrlBuilder("https://INSTANCE.squadlist.co.uk", instanceConfig, seoLinkBuilder, "", ""); + assertEquals("https://twrc.squadlist.co.uk/meh", urlBuilder.applicationUrl("/meh")); + } - @Test - public void webcalFeedsAreHttpEvenIfTheMainSiteIsHttps() throws Exception { - final UrlBuilder urlBuilder = new UrlBuilder("https://INSTANCE.squadlist.co.uk", instanceConfig, seoLinkBuilder, null, null); - assertEquals("webcal://twrc.squadlist.co.uk/ical?user=a-user&key=9329bb2beff6556bf2698163732bb9a2", urlBuilder.outingsIcal("a-user", instance)); - } + @Test + public void canComposeWebcalFeedUrlFromBaseUrl() throws Exception { + final UrlBuilder urlBuilder = new UrlBuilder("http://localhost", instanceConfig, seoLinkBuilder, "", ""); + assertEquals("webcal://localhost/ical?user=a-user&key=9329bb2beff6556bf2698163732bb9a2", urlBuilder.outingsIcal("a-user", instance)); + } - @Test - public void canComposeRssFeedUrlFromBaseUrl() throws Exception { - final UrlBuilder urlBuilder = new UrlBuilder("https://INSTANCE.squadlist.co.uk", instanceConfig, seoLinkBuilder, null, null); - assertEquals("https://twrc.squadlist.co.uk/rss?user=a-user&key=9329bb2beff6556bf2698163732bb9a2", urlBuilder.outingsRss("a-user", instance)); - } + @Test + public void webcalFeedsAreHttpEvenIfTheMainSiteIsHttps() throws Exception { + final UrlBuilder urlBuilder = new UrlBuilder("https://INSTANCE.squadlist.co.uk", instanceConfig, seoLinkBuilder, "", ""); + assertEquals("webcal://twrc.squadlist.co.uk/ical?user=a-user&key=9329bb2beff6556bf2698163732bb9a2", urlBuilder.outingsIcal("a-user", instance)); + } - @Test - public void canLinkToGoverningBodyPage() throws Exception { - final UrlBuilder urlBuilder = new UrlBuilder("https://localhost", instanceConfig, seoLinkBuilder, null, null); - assertEquals("https://localhost/governing-body/british-rowing", urlBuilder.governingBody(new BritishRowing())); - } + @Test + public void canComposeRssFeedUrlFromBaseUrl() throws Exception { + final UrlBuilder urlBuilder = new UrlBuilder("https://INSTANCE.squadlist.co.uk", instanceConfig, seoLinkBuilder, "", ""); + assertEquals("https://twrc.squadlist.co.uk/rss?user=a-user&key=9329bb2beff6556bf2698163732bb9a2", urlBuilder.outingsRss("a-user", instance)); + } - @Test - public void canComposeMailtoListUrl() throws Exception { - final UrlBuilder urlBuilder = new UrlBuilder("https://localhost", instanceConfig, seoLinkBuilder, null, null); - final List emails = Lists.newArrayList("auser@localhost", "anotheruser@localhost"); - assertEquals("mailto:auser@localhost,anotheruser@localhost", urlBuilder.mailto(emails)); - } + @Test + public void canLinkToGoverningBodyPage() throws Exception { + final UrlBuilder urlBuilder = new UrlBuilder("https://localhost", instanceConfig, seoLinkBuilder, "", ""); + assertEquals("https://localhost/governing-body/british-rowing", urlBuilder.governingBody(new BritishRowing())); + } + + @Test + public void canComposeMailtoListUrl() throws Exception { + final UrlBuilder urlBuilder = new UrlBuilder("https://localhost", instanceConfig, seoLinkBuilder, "", ""); + final List emails = Lists.newArrayList("auser@localhost", "anotheruser@localhost"); + assertEquals("mailto:auser@localhost,anotheruser@localhost", urlBuilder.mailto(emails)); + } } \ No newline at end of file