├── .elasticbeanstalk ├── .ebextensions │ ├── 00-set-spring-boot-port.config │ └── nginx │ │ └── conf.d │ │ └── proxy.conf ├── Procfile └── config.yml ├── .gitignore ├── README.md ├── init-dev-environment.sh ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── expercise │ │ ├── Application.java │ │ ├── configuration │ │ ├── ResourceBundleMessageSources.java │ │ ├── SpringSecurityConfiguration.java │ │ ├── SpringSessionRedisConfiguration.java │ │ ├── SpringSocialConfiguration.java │ │ ├── SpringWebMvcConfiguration.java │ │ └── redis │ │ │ ├── EmbeddedRedis.java │ │ │ └── RedisConfiguration.java │ │ ├── controller │ │ ├── BaseManagementController.java │ │ ├── HomeController.java │ │ ├── MessagesForClientSideController.java │ │ ├── RedirectUtils.java │ │ ├── caching │ │ │ └── CachingController.java │ │ ├── challenge │ │ │ ├── ChallengeController.java │ │ │ ├── ChallengeListingController.java │ │ │ ├── ChallengeManagementController.java │ │ │ ├── StartCodingController.java │ │ │ ├── TagsChallengeController.java │ │ │ ├── management │ │ │ │ ├── ChallengeListManagementController.java │ │ │ │ └── TagsManagementController.java │ │ │ └── model │ │ │ │ ├── ChallengeModel.java │ │ │ │ ├── ChallengeResetModel.java │ │ │ │ ├── SaveChallengeResponse.java │ │ │ │ ├── SolutionFromUser.java │ │ │ │ ├── TagModel.java │ │ │ │ └── UserSolutionModel.java │ │ ├── error │ │ │ └── ErrorController.java │ │ ├── leaderboard │ │ │ └── LeaderBoardController.java │ │ ├── user │ │ │ ├── ForgotMyPasswordController.java │ │ │ ├── ProfileController.java │ │ │ ├── SignInController.java │ │ │ ├── SignUpController.java │ │ │ ├── management │ │ │ │ └── UserListingController.java │ │ │ └── model │ │ │ │ ├── EmailRequestModel.java │ │ │ │ ├── ForgotMyPasswordResponse.java │ │ │ │ ├── PasswordModel.java │ │ │ │ ├── PasswordResetModel.java │ │ │ │ └── UserModel.java │ │ └── utils │ │ │ └── BrowserCacheableContent.java │ │ ├── domain │ │ ├── BaseEntity.java │ │ ├── PrioritizedEntity.java │ │ ├── challenge │ │ │ ├── Challenge.java │ │ │ ├── ChallengeInputType.java │ │ │ ├── ChallengeType.java │ │ │ ├── Solution.java │ │ │ ├── TestCase.java │ │ │ ├── TestCaseInputValue.java │ │ │ └── UserPoint.java │ │ ├── quote │ │ │ └── Quote.java │ │ ├── token │ │ │ ├── Token.java │ │ │ └── TokenType.java │ │ └── user │ │ │ ├── RememberMeToken.java │ │ │ ├── SocialUserDetails.java │ │ │ ├── User.java │ │ │ └── UserConnection.java │ │ ├── enums │ │ ├── ChallengeListingMode.java │ │ ├── DataType.java │ │ ├── Lingo.java │ │ ├── ManagementMode.java │ │ ├── ProgrammingLanguage.java │ │ ├── SocialSignInProvider.java │ │ └── UserRole.java │ │ ├── exception │ │ ├── ExperciseGenericException.java │ │ └── ExperciseJsonException.java │ │ ├── interceptor │ │ ├── CommonViewParamsInterceptor.java │ │ └── SetLocaleInterceptor.java │ │ ├── interpreter │ │ ├── InterpretRequest.java │ │ ├── InterpretResponse.java │ │ ├── Interpreter.java │ │ ├── InterpreterClient.java │ │ ├── InterpreterException.java │ │ ├── InterpreterFailureType.java │ │ ├── InterpreterResult.java │ │ ├── TestCaseModel.java │ │ ├── TestCaseResult.java │ │ ├── TestCaseWithResult.java │ │ ├── TestCasesWithSourceCacheModel.java │ │ ├── TestCasesWithSourceModel.java │ │ └── javascript │ │ │ └── JavaScriptInterpreter.java │ │ ├── repository │ │ ├── BaseRepository.java │ │ ├── challenge │ │ │ ├── ChallengeRepository.java │ │ │ ├── SolutionRepository.java │ │ │ └── UserPointRepository.java │ │ ├── quote │ │ │ └── QuoteRepository.java │ │ └── user │ │ │ ├── RememberMeTokenRepository.java │ │ │ ├── TokenRepository.java │ │ │ ├── UserConnectionRepository.java │ │ │ └── UserRepository.java │ │ ├── service │ │ ├── cache │ │ │ ├── JsonRedisTemplate.java │ │ │ ├── ObjectRedisTemplate.java │ │ │ └── RedisCacheService.java │ │ ├── challenge │ │ │ ├── ChallengeDisplayRule.java │ │ │ ├── ChallengeModelHelper.java │ │ │ ├── ChallengeService.java │ │ │ ├── LeaderBoardCalculatorService.java │ │ │ ├── LeaderBoardService.java │ │ │ ├── LeaderBoardWarmUpUtil.java │ │ │ ├── SolutionCountService.java │ │ │ ├── SolutionService.java │ │ │ ├── SolutionValidationService.java │ │ │ ├── TagIndexService.java │ │ │ ├── TagService.java │ │ │ ├── UserPointService.java │ │ │ ├── UserTestCaseStateService.java │ │ │ ├── action │ │ │ │ ├── PostEvaluationAction.java │ │ │ │ ├── PostEvaluationExecutor.java │ │ │ │ ├── PreEvaluationAction.java │ │ │ │ ├── PreEvaluationExecutor.java │ │ │ │ └── postaction │ │ │ │ │ ├── CreateKataSolutionResponsePostAction.java │ │ │ │ │ ├── CreateSolutionResponsePostAction.java │ │ │ │ │ ├── GiveSuccessPointPostAction.java │ │ │ │ │ ├── InterpretationFailureCheckForTestCasesPostAction.java │ │ │ │ │ ├── NotificationWhenChallengeCompletedPostAction.java │ │ │ │ │ ├── PrepareChallengeUserSolutionsPostAction.java │ │ │ │ │ ├── SaveUserSolutionPostAction.java │ │ │ │ │ └── SaveUserTestCaseStatePostAction.java │ │ │ └── model │ │ │ │ ├── ChallengeEvaluationContext.java │ │ │ │ ├── ChallengeSolutionStatus.java │ │ │ │ ├── LeaderBoardModel.java │ │ │ │ └── SolutionValidationResult.java │ │ ├── configuration │ │ │ └── Configurations.java │ │ ├── email │ │ │ ├── EmailSenderService.java │ │ │ ├── EmailService.java │ │ │ ├── EmailTemplateProcessor.java │ │ │ ├── SendGridEmailSenderService.java │ │ │ ├── TemplateEngineWrapper.java │ │ │ └── model │ │ │ │ └── Email.java │ │ ├── i18n │ │ │ └── MessageService.java │ │ ├── language │ │ │ ├── JavaScriptSignatureGenerator.java │ │ │ ├── JavaSignatureGenerator.java │ │ │ ├── Python2SignatureGenerator.java │ │ │ ├── Python3SignatureGenerator.java │ │ │ ├── SignatureGenerator.java │ │ │ └── SignatureGeneratorService.java │ │ ├── notification │ │ │ ├── SlackMessage.java │ │ │ └── SlackNotificationService.java │ │ ├── quote │ │ │ └── QuoteService.java │ │ ├── user │ │ │ ├── AuthenticationService.java │ │ │ ├── CurrentUserHolder.java │ │ │ ├── ForgotMyPasswordService.java │ │ │ ├── PersistentRememberMeTokenRepositoryService.java │ │ │ ├── SocialSignInAdapter.java │ │ │ ├── SocialUserDetailsHelper.java │ │ │ ├── SocialUserDetailsProvider.java │ │ │ ├── UserDetailsProvider.java │ │ │ └── UserService.java │ │ └── util │ │ │ ├── Prioritized.java │ │ │ ├── PrioritySorter.java │ │ │ ├── TokenService.java │ │ │ └── UrlService.java │ │ └── utils │ │ ├── Clock.java │ │ ├── Constants.java │ │ ├── CookieUtils.java │ │ ├── DateUtils.java │ │ ├── EnvironmentUtils.java │ │ ├── JsonUtils.java │ │ ├── NumberUtils.java │ │ ├── PasswordEncoder.java │ │ ├── TextUtils.java │ │ ├── UrlUtils.java │ │ ├── caching │ │ ├── Caching.java │ │ └── CachingSubject.java │ │ ├── collection │ │ ├── MapBuilder.java │ │ └── RandomElement.java │ │ └── validation │ │ ├── BaseValidator.java │ │ ├── EmailUniquenessValidator.java │ │ ├── PasswordMatch.java │ │ ├── PasswordMatchValidator.java │ │ ├── SaveChallengeValidator.java │ │ ├── UniqueEmail.java │ │ └── ValidationUtils.java └── resources │ ├── application-dev.properties │ ├── application-prod.properties │ ├── application.properties │ ├── build.properties │ ├── db │ └── changelog │ │ ├── change-sets │ │ ├── change-set-1.xml │ │ ├── dev-change-set-1.xml │ │ └── initial-change-set.xml │ │ └── changelog-master.xml │ ├── emails │ ├── emailLayout.html │ └── forgotMyPasswordEmail.html │ ├── messagesForEmails_en.properties │ ├── messagesForEmails_pt.properties │ ├── messagesForEmails_tr.properties │ ├── messages_en.properties │ ├── messages_pt.properties │ ├── messages_tr.properties │ ├── public │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── black-rose.ttf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── img │ │ ├── ATTRIBUTION.md │ │ ├── assistant-robot.svg │ │ ├── background │ │ │ ├── green-landscape.svg │ │ │ └── programming-on-the-cloud.svg │ │ ├── expercise-logo-rounded.png │ │ ├── expercise-logo.png │ │ ├── flags │ │ │ ├── br.svg │ │ │ ├── tr.svg │ │ │ └── us.svg │ │ ├── java8-logo.png │ │ ├── javascript-logo.png │ │ ├── python-logo.png │ │ ├── sprite-green.png │ │ ├── sprite-orange.png │ │ └── sprites │ │ │ └── avatars-sprite.svg │ ├── js │ │ ├── bootstrap │ │ │ ├── bootbox.min.js │ │ │ ├── bootstrap-tagsinput.min.js │ │ │ ├── bootstrap-virtual-assistant.js │ │ │ └── bootstrap.min.js │ │ ├── bootstrapper.js │ │ ├── challenge.js │ │ ├── challengeManagement.js │ │ ├── codeEditor.js │ │ ├── codemirror │ │ │ ├── anyword-hint.js │ │ │ ├── codemirror.js │ │ │ ├── javascript-hint.js │ │ │ ├── mode │ │ │ │ ├── clike.js │ │ │ │ ├── javascript.js │ │ │ │ └── python.js │ │ │ ├── show-hint.js │ │ │ └── vim.js │ │ ├── expercise.js │ │ ├── forgottenPassword.js │ │ ├── header.js │ │ ├── home.js │ │ ├── lib │ │ │ ├── hopscotch.min.js │ │ │ ├── jquery-ui.min.js │ │ │ ├── jquery.cookie.js │ │ │ ├── jquery.fullscreen.min.js │ │ │ ├── jquery.hotkeys.js │ │ │ ├── jquery.min.js │ │ │ └── less.min.js │ │ ├── locale.js │ │ ├── profile.js │ │ ├── tagListing.js │ │ └── utils.js │ └── style │ │ ├── addUpdateChallenge.less │ │ ├── ambiance.css │ │ ├── avatars.less │ │ ├── bootstrap-tagsinput.css │ │ ├── bootstrap-virtual-assistant.css │ │ ├── bootstrap.min.css │ │ ├── challenge.less │ │ ├── codemirror.css │ │ ├── common.less │ │ ├── expercise.less │ │ ├── font-awesome.min.css │ │ ├── footer.less │ │ ├── header.less │ │ ├── hopscotch.min.css │ │ ├── index.less │ │ ├── login.less │ │ ├── profile.less │ │ ├── show-hint.css │ │ └── tagListing.less │ └── templates │ ├── challenge │ ├── challenge.html │ ├── challengeList.html │ └── manageChallenge.html │ ├── error │ └── errorPage.html │ ├── fragments │ ├── footer.html │ ├── header.html │ ├── inputHiddens.html │ └── scripts.html │ ├── index.html │ ├── leaderBoard │ └── leaderBoard.html │ ├── mainLayout.html │ ├── signin.html │ ├── signup.html │ ├── tag │ ├── tagsChallengeList.html │ └── tagsList.html │ └── user │ ├── forgotMyPassword.html │ ├── passwordReset.html │ ├── profile.html │ └── userList.html └── test ├── groovy └── com │ └── expercise │ ├── controller │ ├── RedirectUtilsSpec.groovy │ └── user │ │ └── ForgotMyPasswordControllerSpec.groovy │ ├── service │ ├── challenge │ │ ├── action │ │ │ └── postaction │ │ │ │ ├── CreateKataSolutionResponsePostActionSpec.groovy │ │ │ │ └── InterpretationFailureCheckForTestCasesPostActionSpec.groovy │ │ └── model │ │ │ └── ChallengeEvaluationContextSpec.groovy │ ├── email │ │ ├── EmailServiceSpec.groovy │ │ ├── EmailTemplateProcessorSpec.groovy │ │ └── SendGridEmailServiceSpec.groovy │ ├── user │ │ └── ForgotMyPasswordServiceSpec.groovy │ └── util │ │ ├── TokenServiceSpec.groovy │ │ └── UrlServiceSpec.groovy │ └── utils │ ├── NumberUtilsSpec.groovy │ └── UrlUtilsSpec.groovy ├── java └── com │ └── expercise │ ├── BaseSpringIntegrationTest.java │ ├── controller │ ├── HomeControllerTest.java │ └── user │ │ └── management │ │ └── UserListingControllerTest.java │ ├── domain │ └── user │ │ └── UserTest.java │ ├── enums │ └── LingoTest.java │ ├── interceptor │ └── CommonViewParamsInterceptorTest.java │ ├── interpreter │ ├── InfiniteLoopTestInterpreter.java │ ├── InterpreterTest.java │ └── TestCaseModelTest.java │ ├── repository │ └── challenge │ │ ├── ChallengeRepositoryTest.java │ │ ├── SolutionRepositoryTest.java │ │ └── UserPointRepositoryTest.java │ ├── service │ ├── challenge │ │ ├── ChallengeDisplayRuleTest.java │ │ ├── ChallengeServiceTest.java │ │ ├── LeaderBoardServiceTest.java │ │ ├── SolutionServiceTest.java │ │ ├── UserPointServiceTest.java │ │ └── action │ │ │ ├── PostEvaluationExecutorTest.java │ │ │ ├── PreEvaluationExecutorTest.java │ │ │ └── postaction │ │ │ ├── CreateSolutionResponsePostActionTest.java │ │ │ ├── GiveSuccessPointPostActionTest.java │ │ │ ├── PrepareChallengeUserSolutionsPostActionTest.java │ │ │ └── SaveUserSolutionPostActionTest.java │ ├── email │ │ └── EmailTemplateProcessorTest.java │ ├── language │ │ ├── JavaScriptSignatureGeneratorTest.java │ │ ├── JavaSignatureGeneratorTest.java │ │ └── Python2SignatureGeneratorTest.java │ ├── quote │ │ └── QuoteServiceTest.java │ └── user │ │ └── UserServiceTest.java │ ├── testutils │ ├── FileTestUtils.java │ ├── asserts │ │ └── Asserts.java │ ├── builder │ │ ├── BaseEntityBuilder.java │ │ ├── BasePrioritizedEntityBuilder.java │ │ ├── ChallengeBuilder.java │ │ ├── ChallengeInputTypeBuilder.java │ │ ├── QuoteBuilder.java │ │ ├── SolutionBuilder.java │ │ ├── TestCaseBuilder.java │ │ ├── TestCaseInputValueBuilder.java │ │ ├── UserBuilder.java │ │ └── UserPointBuilder.java │ └── matchers │ │ └── Matchers.java │ └── utils │ ├── CookieUtilsTest.java │ └── JsonUtilsTest.java └── resources ├── application.properties └── renderedEmails └── forgotMyPasswordEmail.html /.elasticbeanstalk/.ebextensions/00-set-spring-boot-port.config: -------------------------------------------------------------------------------- 1 | option_settings: 2 | - namespace: aws:elasticbeanstalk:application:environment 3 | option_name: PORT 4 | value: 8080 -------------------------------------------------------------------------------- /.elasticbeanstalk/.ebextensions/nginx/conf.d/proxy.conf: -------------------------------------------------------------------------------- 1 | client_max_body_size 10M; -------------------------------------------------------------------------------- /.elasticbeanstalk/Procfile: -------------------------------------------------------------------------------- 1 | web: java -Dspring.profiles.active=prod -Djava.security.egd=file:/dev/./urandom -jar coding-expercise.jar -------------------------------------------------------------------------------- /.elasticbeanstalk/config.yml: -------------------------------------------------------------------------------- 1 | deploy: 2 | artifact: target/elasticbeanstalk-artifact.zip 3 | global: 4 | application_name: coding-expercise 5 | branch: null 6 | default_ec2_keyname: coding-expercise 7 | default_platform: Java 8 8 | default_region: us-east-1 9 | instance_profile: null 10 | platform_name: null 11 | platform_version: null 12 | profile: null 13 | repository: null 14 | sc: null 15 | workspace_type: Application 16 | branch-defaults: 17 | master: 18 | environment: null 19 | group_suffix: null -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | *.iml 6 | .idea 7 | .DS_Store 8 | .metadata 9 | .externalToolBuilders 10 | .cache 11 | .springBeans 12 | environmentSpecific.properties 13 | expercise.min*.css 14 | /overlays/ 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | coding-expercise 2 | ================ 3 | 4 | Learn to code. Code to make the dreams come true. 5 | 6 | ![expercise-main-page](https://cloud.githubusercontent.com/assets/327434/12930722/a22e3d04-cf82-11e5-931f-3d578bbcaf3e.png) 7 | -------------------------------------------------------------------------------- /init-dev-environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # STOP CONTAINERS 4 | 5 | echo "PostgreSQL container is being stopped..." 6 | docker rm -f codingexpercise-postgres || true 7 | 8 | echo "Redis container is being stopped..." 9 | docker rm -f codingexpercise-redis || true 10 | 11 | # START CONTAINERS 12 | 13 | echo "PostgreSQL container is being started..." 14 | docker run --name codingexpercise-postgres -d -p 5432:5432 -e POSTGRES_PASSWORD=123qwe -e POSTGRES_USER=root -e POSTGRES_DB=codingexpercise postgres 15 | 16 | echo "Redis container is being started..." 17 | docker run --name codingexpercise-redis -d -p 6379:6379 redis 18 | 19 | echo "All containers have been started successfully." -------------------------------------------------------------------------------- /src/main/java/com/expercise/Application.java: -------------------------------------------------------------------------------- 1 | package com.expercise; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 6 | import org.springframework.context.annotation.PropertySource; 7 | 8 | @SpringBootApplication 9 | @EnableAspectJAutoProxy 10 | @PropertySource(value = "build.properties", ignoreResourceNotFound = true) 11 | public class Application { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(Application.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/configuration/ResourceBundleMessageSources.java: -------------------------------------------------------------------------------- 1 | package com.expercise.configuration; 2 | 3 | import org.springframework.context.i18n.LocaleContextHolder; 4 | import org.springframework.context.support.ResourceBundleMessageSource; 5 | 6 | import java.util.ResourceBundle; 7 | 8 | public class ResourceBundleMessageSources extends ResourceBundleMessageSource { 9 | 10 | public ResourceBundle getApplicationMessages() { 11 | return getResourceBundle("messages", LocaleContextHolder.getLocale()); 12 | } 13 | 14 | public ResourceBundle getEmailMessages() { 15 | return getResourceBundle("messagesForEmails", LocaleContextHolder.getLocale()); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/configuration/SpringSessionRedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.expercise.configuration; 2 | 3 | import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; 4 | 5 | @EnableRedisHttpSession 6 | public class SpringSessionRedisConfiguration { 7 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/configuration/redis/EmbeddedRedis.java: -------------------------------------------------------------------------------- 1 | package com.expercise.configuration.redis; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.stereotype.Component; 8 | import redis.embedded.RedisServer; 9 | 10 | import javax.annotation.PostConstruct; 11 | import javax.annotation.PreDestroy; 12 | 13 | @Component 14 | @Profile("!prod") 15 | public class EmbeddedRedis { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(EmbeddedRedis.class); 18 | 19 | @Value("${spring.redis.port}") 20 | private int redisPort; 21 | 22 | private RedisServer redisServer; 23 | 24 | @PostConstruct 25 | public void startRedis() { 26 | try { 27 | redisServer = new RedisServer(redisPort); 28 | redisServer.start(); 29 | } catch (Exception e) { 30 | LOGGER.error("Embedded redis couldn't be started: ", e); 31 | } 32 | } 33 | 34 | @PreDestroy 35 | public void stopRedis() { 36 | try { 37 | redisServer.stop(); 38 | } catch (Exception e) { 39 | LOGGER.error("Embedded redis couldn't be stopped: ", e); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/BaseManagementController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | @Controller 7 | @RequestMapping("/manage") 8 | public abstract class BaseManagementController { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller; 2 | 3 | import com.expercise.service.quote.QuoteService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.servlet.ModelAndView; 8 | 9 | @Controller 10 | @RequestMapping("/") 11 | public class HomeController { 12 | 13 | @Autowired 14 | private QuoteService quoteService; 15 | 16 | @RequestMapping 17 | public ModelAndView homePage() { 18 | ModelAndView modelAndView = new ModelAndView("index"); 19 | modelAndView.addObject("quote", quoteService.randomQuote()); 20 | return modelAndView; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/MessagesForClientSideController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller; 2 | 3 | import com.expercise.controller.utils.BrowserCacheableContent; 4 | import com.expercise.service.i18n.MessageService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | @Controller 16 | @RequestMapping("/generatedResources/js") 17 | public class MessagesForClientSideController { 18 | 19 | @Autowired 20 | private MessageService messageService; 21 | 22 | @RequestMapping(value = "/messages_{lingo}-{buildId}.js", produces = "application/javascript; charset=utf-8") 23 | @ResponseBody 24 | public String getMessages(HttpServletRequest request, HttpServletResponse response) { 25 | return new BrowserCacheableContent() { 26 | @Override 27 | public String generateContent() { 28 | return prepareMessagesForClientSide(); 29 | } 30 | }.getContent(request, response); 31 | } 32 | 33 | private String prepareMessagesForClientSide() { 34 | List keyValuePairsAsList = messageService.getAllMessages().entrySet().stream() 35 | .map(e -> String.format("\"%s\": \"%s\"", e.getKey(), e.getValue().replace("\"", "\\\""))) 36 | .collect(Collectors.toList()); 37 | 38 | return "var messages = {\n\t" + String.join(",\n\t", keyValuePairsAsList) + "\n};"; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/RedirectUtils.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller; 2 | 3 | import org.springframework.web.servlet.ModelAndView; 4 | 5 | public final class RedirectUtils { 6 | 7 | private RedirectUtils() { 8 | } 9 | 10 | public static ModelAndView redirectHome() { 11 | return redirectTo("/"); 12 | } 13 | 14 | public static ModelAndView redirectTagsForNewMember() { 15 | return redirectTo("/start-coding?" + "newMember"); 16 | } 17 | 18 | public static ModelAndView redirectLoginFor(String purpose) { 19 | return redirectTo("/signin?" + purpose); 20 | } 21 | 22 | public static ModelAndView redirectLogin() { 23 | return redirectTo("/signin"); 24 | } 25 | 26 | public static ModelAndView redirectProfile() { 27 | return redirectTo("/user"); 28 | } 29 | 30 | public static ModelAndView redirect403() { 31 | return redirectTo("/403"); 32 | } 33 | 34 | public static ModelAndView redirect404() { 35 | return redirectTo("/404"); 36 | } 37 | 38 | public static ModelAndView redirectTo(String url) { 39 | return new ModelAndView("redirect:" + url); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/caching/CachingController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.caching; 2 | 3 | import com.expercise.utils.caching.Caching; 4 | import com.expercise.utils.caching.CachingSubject; 5 | import com.expercise.controller.BaseManagementController; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.ResponseBody; 15 | 16 | @Controller 17 | public class CachingController extends BaseManagementController { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(CachingController.class); 20 | 21 | @Autowired 22 | private ApplicationContext applicationContext; 23 | 24 | @RequestMapping("/flushCache/{cachingSubject}") 25 | @ResponseBody 26 | public String flushCache(@PathVariable String cachingSubject) { 27 | try { 28 | Caching cachingBean = applicationContext.getBean(CachingSubject.valueOf(cachingSubject).getCachingClass()); 29 | cachingBean.flush(); 30 | } catch (Exception e) { 31 | LOGGER.error("Exception occurred while flushing caching subject: " + cachingSubject, e); 32 | return HttpStatus.BAD_REQUEST.getReasonPhrase(); 33 | } 34 | 35 | return HttpStatus.OK.getReasonPhrase(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/ChallengeListingController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.enums.ChallengeListingMode; 5 | import com.expercise.service.challenge.ChallengeService; 6 | import com.expercise.service.challenge.SolutionCountService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.servlet.ModelAndView; 11 | 12 | import java.util.List; 13 | 14 | @Controller 15 | public class ChallengeListingController { 16 | 17 | @Autowired 18 | private ChallengeService challengeService; 19 | 20 | @Autowired 21 | private SolutionCountService solutionCountService; 22 | 23 | @RequestMapping("/challenges/myChallenges") 24 | public ModelAndView listChallengesOfUser() { 25 | List challenges = challengeService.findAllChallengesOfUser(); 26 | return prepareModelAndViewForListing(challenges, ChallengeListingMode.User.name()); 27 | } 28 | 29 | private ModelAndView prepareModelAndViewForListing(List challenges, String mode) { 30 | ModelAndView modelAndView = new ModelAndView("challenge/challengeList"); 31 | modelAndView.addObject("challenges", challenges); 32 | modelAndView.addObject("solutionCountMap", solutionCountService.prepareSolutionCountMapFor(challenges)); 33 | modelAndView.addObject("mode", mode); 34 | return modelAndView; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/StartCodingController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge; 2 | 3 | import com.expercise.controller.challenge.model.TagModel; 4 | import com.expercise.domain.challenge.Challenge; 5 | import com.expercise.service.challenge.ChallengeService; 6 | import com.expercise.service.challenge.TagService; 7 | import com.expercise.service.user.UserService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import java.util.List; 14 | import java.util.Optional; 15 | 16 | @Controller 17 | public class StartCodingController { 18 | 19 | @Autowired 20 | private ChallengeService challengeService; 21 | 22 | @Autowired 23 | private UserService userService; 24 | 25 | @Autowired 26 | private TagService tagService; 27 | 28 | @RequestMapping("/start-coding") 29 | public ModelAndView listTagsWithChallengeCounts() { 30 | if (userService.isNewbie()) { 31 | Optional defaultChallenge = challengeService.getDefaultChallenge(); 32 | if (defaultChallenge.isPresent()) { 33 | return new ModelAndView("redirect:/challenges/" + defaultChallenge.get().getId() + "?newMember"); 34 | } 35 | } 36 | 37 | ModelAndView modelAndView = new ModelAndView("tag/tagsList"); 38 | List tagModels = tagService.prepareTagModels(); 39 | modelAndView.addObject("tagList", tagModels); 40 | 41 | return modelAndView; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/TagsChallengeController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.service.challenge.SolutionCountService; 5 | import com.expercise.service.challenge.SolutionService; 6 | import com.expercise.service.challenge.TagService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import java.util.List; 14 | 15 | @Controller 16 | @RequestMapping("/challenges") 17 | public class TagsChallengeController { 18 | 19 | @Autowired 20 | private TagService tagService; 21 | 22 | @Autowired 23 | private SolutionCountService solutionCountService; 24 | 25 | @Autowired 26 | private SolutionService solutionService; 27 | 28 | @RequestMapping("/tags/{tags}") 29 | public ModelAndView listChallengesForTags(@PathVariable String tags) { 30 | ModelAndView modelAndView = new ModelAndView("tag/tagsChallengeList"); 31 | modelAndView.addObject("tags", tags); 32 | 33 | List challengeList = tagService.getChallengesForTags(tags); 34 | modelAndView.addObject("challengeList", challengeList); 35 | modelAndView.addObject("solutionCountMap", solutionCountService.prepareSolutionCountMapFor(challengeList)); 36 | modelAndView.addObject("solvedChallengesByCurrentUser", solutionService.getSolvedChallengesOfCurrentUser()); 37 | 38 | return modelAndView; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/management/ChallengeListManagementController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge.management; 2 | 3 | import com.expercise.controller.BaseManagementController; 4 | import com.expercise.domain.challenge.Challenge; 5 | import com.expercise.enums.ChallengeListingMode; 6 | import com.expercise.service.challenge.ChallengeService; 7 | import com.expercise.service.challenge.SolutionCountService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.servlet.ModelAndView; 13 | 14 | import java.util.List; 15 | 16 | @Controller 17 | public class ChallengeListManagementController extends BaseManagementController { 18 | 19 | @Autowired 20 | private ChallengeService challengeService; 21 | 22 | @Autowired 23 | private SolutionCountService solutionCountService; 24 | 25 | @GetMapping("/challenges") 26 | public ModelAndView listChallengesForAdmin() { 27 | List challenges = challengeService.findAll(); 28 | ModelAndView modelAndView = new ModelAndView("challenge/challengeList"); 29 | modelAndView.addObject("challenges", challenges); 30 | modelAndView.addObject("solutionCountMap", solutionCountService.prepareSolutionCountMapFor(challenges)); 31 | modelAndView.addObject("mode", ChallengeListingMode.Admin.name()); 32 | return modelAndView; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/management/TagsManagementController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge.management; 2 | 3 | import com.expercise.controller.BaseManagementController; 4 | import com.expercise.service.challenge.TagIndexService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | 9 | @Controller 10 | public class TagsManagementController extends BaseManagementController { 11 | 12 | @Autowired 13 | private TagIndexService tagIndexService; 14 | 15 | @GetMapping("/tags/re-index") 16 | public void listChallengesForAdmin() { 17 | tagIndexService.indexTags(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/model/ChallengeResetModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge.model; 2 | 3 | public class ChallengeResetModel { 4 | 5 | private Long challengeId; 6 | 7 | private String language; 8 | 9 | public Long getChallengeId() { 10 | return challengeId; 11 | } 12 | 13 | public void setChallengeId(Long challengeId) { 14 | this.challengeId = challengeId; 15 | } 16 | 17 | public String getLanguage() { 18 | return language; 19 | } 20 | 21 | public void setLanguage(String language) { 22 | this.language = language; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/model/SaveChallengeResponse.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class SaveChallengeResponse { 7 | 8 | private boolean success = false; 9 | 10 | private Long challengeId; 11 | 12 | private List errorCodes = new ArrayList<>(); 13 | 14 | public static SaveChallengeResponse successResponse(Long challengeId) { 15 | SaveChallengeResponse response = new SaveChallengeResponse(); 16 | response.setSuccess(true); 17 | response.setChallengeId(challengeId); 18 | return response; 19 | } 20 | 21 | public static SaveChallengeResponse failedResponse(List errorCodes) { 22 | SaveChallengeResponse response = new SaveChallengeResponse(); 23 | response.setSuccess(false); 24 | response.setErrorCodes(errorCodes); 25 | return response; 26 | } 27 | 28 | public boolean isSuccess() { 29 | return success; 30 | } 31 | 32 | public void setSuccess(boolean success) { 33 | this.success = success; 34 | } 35 | 36 | public Long getChallengeId() { 37 | return challengeId; 38 | } 39 | 40 | public void setChallengeId(Long challengeId) { 41 | this.challengeId = challengeId; 42 | } 43 | 44 | public List getErrorCodes() { 45 | return errorCodes; 46 | } 47 | 48 | public void setErrorCodes(List errorCodes) { 49 | this.errorCodes = errorCodes; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/model/SolutionFromUser.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge.model; 2 | 3 | import com.expercise.enums.ProgrammingLanguage; 4 | 5 | public class SolutionFromUser { 6 | 7 | private String solution; 8 | 9 | private String language; 10 | 11 | private Long challengeId; 12 | 13 | public String getSolution() { 14 | return solution; 15 | } 16 | 17 | public void setSolution(String solution) { 18 | this.solution = solution; 19 | } 20 | 21 | public void setLanguage(String language) { 22 | this.language = language; 23 | } 24 | 25 | public Long getChallengeId() { 26 | return challengeId; 27 | } 28 | 29 | public void setChallengeId(Long challengeId) { 30 | this.challengeId = challengeId; 31 | } 32 | 33 | public ProgrammingLanguage getProgrammingLanguage() { 34 | return ProgrammingLanguage.getLanguage(language).get(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/challenge/model/TagModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.challenge.model; 2 | 3 | public class TagModel { 4 | 5 | private String name; 6 | 7 | private int count; 8 | 9 | private int rank = 1; 10 | 11 | public TagModel() { 12 | } 13 | 14 | public TagModel(String name, int count) { 15 | this.name = name; 16 | this.count = count; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public void setName(String name) { 24 | this.name = name; 25 | } 26 | 27 | public int getCount() { 28 | return count; 29 | } 30 | 31 | public void setCount(int count) { 32 | this.count = count; 33 | } 34 | 35 | public int getRank() { 36 | return rank; 37 | } 38 | 39 | public void setRank(int rank) { 40 | this.rank = rank; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/error/ErrorController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.error; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.servlet.ModelAndView; 6 | 7 | // TODO ufuk: re-implement with @ControllerAdvice and @ExceptionHandler mechanisms 8 | @Controller 9 | public class ErrorController { 10 | 11 | @RequestMapping("/403") 12 | public ModelAndView get403() { 13 | return prepareModelAndViewFor(403); 14 | } 15 | 16 | @RequestMapping("/404") 17 | public ModelAndView get404() { 18 | return prepareModelAndViewFor(404); 19 | } 20 | 21 | @RequestMapping("/500") 22 | public ModelAndView get500() { 23 | return prepareModelAndViewFor(500); 24 | } 25 | 26 | private ModelAndView prepareModelAndViewFor(int errorCode) { 27 | ModelAndView modelAndView = new ModelAndView("error/errorPage"); 28 | modelAndView.addObject("errorCode", errorCode); 29 | return modelAndView; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/leaderboard/LeaderBoardController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.leaderboard; 2 | 3 | import com.expercise.service.challenge.LeaderBoardService; 4 | import com.expercise.service.challenge.model.LeaderBoardModel; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.servlet.ModelAndView; 9 | 10 | import java.util.List; 11 | 12 | @Controller 13 | @RequestMapping("/leaderBoard") 14 | public class LeaderBoardController { 15 | 16 | @Autowired 17 | private LeaderBoardService leaderBoardService; 18 | 19 | @RequestMapping 20 | public ModelAndView leaderBoardLandingPage() { 21 | ModelAndView modelAndView = new ModelAndView("leaderBoard/leaderBoard"); 22 | 23 | List top10Users = leaderBoardService.getTop10UsersInLeaderBoard(); 24 | 25 | modelAndView.addObject("top10Users", top10Users); 26 | 27 | return modelAndView; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/user/SignInController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.user; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.servlet.ModelAndView; 6 | 7 | @Controller 8 | public class SignInController { 9 | 10 | @RequestMapping("/signin") 11 | public ModelAndView loginPage() { 12 | return new ModelAndView("signin"); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/user/management/UserListingController.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.user.management; 2 | 3 | import com.expercise.controller.BaseManagementController; 4 | import com.expercise.service.user.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.servlet.ModelAndView; 9 | 10 | @Controller 11 | public class UserListingController extends BaseManagementController { 12 | 13 | @Autowired 14 | private UserService userService; 15 | 16 | @RequestMapping("/users") 17 | public ModelAndView listUsersForAdmin() { 18 | ModelAndView modelAndView = new ModelAndView("user/userList"); 19 | modelAndView.addObject("users", userService.findAll()); 20 | return modelAndView; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/user/model/EmailRequestModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.user.model; 2 | 3 | public class EmailRequestModel { 4 | 5 | private String email; 6 | 7 | public String getEmail() { 8 | return email; 9 | } 10 | 11 | public void setEmail(String email) { 12 | this.email = email; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/user/model/ForgotMyPasswordResponse.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.user.model; 2 | 3 | public class ForgotMyPasswordResponse { 4 | 5 | private boolean success; 6 | 7 | private String messageKey; 8 | 9 | public static ForgotMyPasswordResponse failedResponse() { 10 | ForgotMyPasswordResponse failedResponse = new ForgotMyPasswordResponse(); 11 | failedResponse.setSuccess(false); 12 | failedResponse.setMessageKey("forgotMyPassword.request.failed"); 13 | return failedResponse; 14 | } 15 | 16 | public static ForgotMyPasswordResponse successResponse() { 17 | ForgotMyPasswordResponse successResponse = new ForgotMyPasswordResponse(); 18 | successResponse.setSuccess(true); 19 | successResponse.setMessageKey("forgotMyPassword.request.success"); 20 | return successResponse; 21 | } 22 | 23 | public boolean isSuccess() { 24 | return success; 25 | } 26 | 27 | public void setSuccess(boolean success) { 28 | this.success = success; 29 | } 30 | 31 | public String getMessageKey() { 32 | return messageKey; 33 | } 34 | 35 | public void setMessageKey(String messageKey) { 36 | this.messageKey = messageKey; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/user/model/PasswordModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.user.model; 2 | 3 | import com.expercise.utils.validation.PasswordMatch; 4 | 5 | import javax.validation.constraints.Size; 6 | 7 | @PasswordMatch 8 | public class PasswordModel { 9 | 10 | @Size(min = 6, max = 16) 11 | private String password; 12 | 13 | private String passwordRetype; 14 | 15 | public String getPassword() { 16 | return password; 17 | } 18 | 19 | public void setPassword(String password) { 20 | this.password = password; 21 | } 22 | 23 | public String getPasswordRetype() { 24 | return passwordRetype; 25 | } 26 | 27 | public void setPasswordRetype(String passwordRetype) { 28 | this.passwordRetype = passwordRetype; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/user/model/PasswordResetModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.user.model; 2 | 3 | import javax.validation.Valid; 4 | 5 | public class PasswordResetModel { 6 | 7 | @Valid 8 | private PasswordModel passwordModel; 9 | 10 | private String token; 11 | 12 | public PasswordModel getPasswordModel() { 13 | return passwordModel; 14 | } 15 | 16 | public void setPasswordModel(PasswordModel passwordModel) { 17 | this.passwordModel = passwordModel; 18 | } 19 | 20 | public String getToken() { 21 | return token; 22 | } 23 | 24 | public void setToken(String token) { 25 | this.token = token; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/user/model/UserModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.user.model; 2 | 3 | import com.expercise.domain.user.User; 4 | import com.expercise.utils.validation.UniqueEmail; 5 | import org.hibernate.validator.constraints.Email; 6 | import org.hibernate.validator.constraints.NotEmpty; 7 | 8 | import javax.validation.constraints.Size; 9 | 10 | public class UserModel { 11 | 12 | @Size(min = 2, max = 15) 13 | private String firstName; 14 | 15 | @Size(min = 2, max = 15) 16 | private String lastName; 17 | 18 | @NotEmpty 19 | @Email 20 | @UniqueEmail 21 | private String email; 22 | 23 | @Size(min = 6, max = 16) 24 | private String password; 25 | 26 | public String getFirstName() { 27 | return firstName; 28 | } 29 | 30 | public void setFirstName(String firstName) { 31 | this.firstName = firstName; 32 | } 33 | 34 | public String getLastName() { 35 | return lastName; 36 | } 37 | 38 | public void setLastName(String lastName) { 39 | this.lastName = lastName; 40 | } 41 | 42 | public String getEmail() { 43 | return email; 44 | } 45 | 46 | public void setEmail(String email) { 47 | this.email = email; 48 | } 49 | 50 | public String getPassword() { 51 | return password; 52 | } 53 | 54 | public void setPassword(String password) { 55 | this.password = password; 56 | } 57 | 58 | public User createUser() { 59 | User user = new User(); 60 | user.setFirstName(firstName); 61 | user.setLastName(lastName); 62 | user.setEmail(email); 63 | user.setPassword(password); 64 | return user; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/controller/utils/BrowserCacheableContent.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.utils; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | public abstract class BrowserCacheableContent { 9 | 10 | public abstract String generateContent(); 11 | 12 | public String getContent(HttpServletRequest request, HttpServletResponse response) { 13 | long ifModifiedSince = request.getDateHeader("If-Modified-Since"); 14 | 15 | if (ifModifiedSince > -1) { 16 | response.setStatus(HttpStatus.NOT_MODIFIED.value()); 17 | return ""; 18 | } 19 | 20 | response.addDateHeader("Last-Modified", System.currentTimeMillis()); 21 | 22 | return generateContent(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain; 2 | 3 | import org.hibernate.Hibernate; 4 | 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import javax.persistence.MappedSuperclass; 9 | 10 | @MappedSuperclass 11 | public abstract class BaseEntity { 12 | 13 | @Id 14 | @GeneratedValue(generator = "ID_GENERATOR", strategy = GenerationType.SEQUENCE) 15 | private Long id; 16 | 17 | public Long getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Long id) { 22 | this.id = id; 23 | } 24 | 25 | public boolean isPersisted() { 26 | return getId() != null; 27 | } 28 | 29 | public boolean isNotPersisted() { 30 | return !isPersisted(); 31 | } 32 | 33 | @Override 34 | public boolean equals(Object toCompare) { 35 | if (this == toCompare) { 36 | return true; 37 | } 38 | 39 | if (toCompare == null) { 40 | return false; 41 | } 42 | 43 | if (Hibernate.getClass(this) != Hibernate.getClass(toCompare)) { 44 | return false; 45 | } 46 | 47 | BaseEntity otherEntity = (BaseEntity) toCompare; 48 | if (getId() == null) { 49 | if (otherEntity.getId() != null) { 50 | return false; 51 | } 52 | } else if (!getId().equals(otherEntity.getId())) { 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | int magicNumber = 13; 62 | return magicNumber + ((getId() == null) ? 0 : getId().hashCode()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/PrioritizedEntity.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain; 2 | 3 | import com.expercise.service.util.Prioritized; 4 | 5 | import javax.persistence.MappedSuperclass; 6 | import java.util.List; 7 | 8 | @MappedSuperclass 9 | public abstract class PrioritizedEntity extends BaseEntity implements Prioritized { 10 | 11 | private int priority; 12 | 13 | public static void prioritize(List prioritizedEntityList) { 14 | int priority = 1; 15 | for (T each : prioritizedEntityList) { 16 | each.setPriority(priority); 17 | priority++; 18 | } 19 | } 20 | 21 | @Override 22 | public int getPriority() { 23 | return priority; 24 | } 25 | 26 | public void setPriority(int priority) { 27 | this.priority = priority; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/challenge/ChallengeInputType.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.challenge; 2 | 3 | import com.expercise.domain.PrioritizedEntity; 4 | import com.expercise.enums.DataType; 5 | 6 | import javax.persistence.*; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Entity 11 | @SequenceGenerator(name = "ID_GENERATOR", sequenceName = "SEQ_CHALLENGE_INPUT_TYPE") 12 | public class ChallengeInputType extends PrioritizedEntity { 13 | 14 | @Enumerated(EnumType.STRING) 15 | private DataType inputType; 16 | 17 | @ManyToOne(fetch = FetchType.LAZY) 18 | private Challenge challenge; 19 | 20 | public static List createFrom(List inputTypes) { 21 | List challengeInputTypes = new ArrayList<>(); 22 | for (DataType inputType : inputTypes) { 23 | ChallengeInputType challengeInputType = new ChallengeInputType(); 24 | challengeInputType.setInputType(inputType); 25 | challengeInputTypes.add(challengeInputType); 26 | } 27 | prioritize(challengeInputTypes); 28 | return challengeInputTypes; 29 | } 30 | 31 | public DataType getInputType() { 32 | return inputType; 33 | } 34 | 35 | public void setInputType(DataType inputType) { 36 | this.inputType = inputType; 37 | } 38 | 39 | public Challenge getChallenge() { 40 | return challenge; 41 | } 42 | 43 | public void setChallenge(Challenge challenge) { 44 | this.challenge = challenge; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/challenge/ChallengeType.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.challenge; 2 | 3 | public enum ChallengeType { 4 | 5 | ALGORITHM, 6 | CODE_KATA 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/challenge/Solution.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.challenge; 2 | 3 | import com.expercise.domain.BaseEntity; 4 | import com.expercise.domain.user.User; 5 | import com.expercise.enums.ProgrammingLanguage; 6 | 7 | import javax.persistence.*; 8 | import java.util.Date; 9 | 10 | @Entity 11 | @SequenceGenerator(name = "ID_GENERATOR", sequenceName = "SEQ_SOLUTION") 12 | public class Solution extends BaseEntity { 13 | 14 | @ManyToOne(fetch = FetchType.LAZY, optional = false) 15 | private User user; 16 | 17 | @ManyToOne(fetch = FetchType.LAZY, optional = false) 18 | private Challenge challenge; 19 | 20 | @Enumerated(EnumType.STRING) 21 | @Column(nullable = false) 22 | private ProgrammingLanguage programmingLanguage; 23 | 24 | @Column(nullable = false, length = 4096) 25 | private String solution; 26 | 27 | @Temporal(TemporalType.TIMESTAMP) 28 | @Column(nullable = false) 29 | private Date createDate; 30 | 31 | public User getUser() { 32 | return user; 33 | } 34 | 35 | public void setUser(User user) { 36 | this.user = user; 37 | } 38 | 39 | public Challenge getChallenge() { 40 | return challenge; 41 | } 42 | 43 | public void setChallenge(Challenge challenge) { 44 | this.challenge = challenge; 45 | } 46 | 47 | public ProgrammingLanguage getProgrammingLanguage() { 48 | return programmingLanguage; 49 | } 50 | 51 | public void setProgrammingLanguage(ProgrammingLanguage programmingLanguage) { 52 | this.programmingLanguage = programmingLanguage; 53 | } 54 | 55 | public String getSolution() { 56 | return solution; 57 | } 58 | 59 | public void setSolution(String solution) { 60 | this.solution = solution; 61 | } 62 | 63 | public Date getCreateDate() { 64 | return createDate; 65 | } 66 | 67 | public void setCreateDate(Date createDate) { 68 | this.createDate = createDate; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/challenge/TestCase.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.challenge; 2 | 3 | import com.expercise.domain.PrioritizedEntity; 4 | import com.expercise.enums.DataType; 5 | import com.expercise.utils.Constants; 6 | 7 | import javax.persistence.*; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Entity 12 | @SequenceGenerator(name = "ID_GENERATOR", sequenceName = "SEQ_TEST_CASE") 13 | public class TestCase extends PrioritizedEntity { 14 | 15 | @ManyToOne(fetch = FetchType.LAZY) 16 | private Challenge challenge; 17 | 18 | @OneToMany(mappedBy = "testCase", fetch = FetchType.EAGER, orphanRemoval = true, cascade = CascadeType.ALL) 19 | @OrderBy("priority") 20 | private List inputs = new ArrayList<>(); 21 | 22 | @Column(nullable = false, length = Constants.MAX_TESTCASE_VALUE_LENGTH) 23 | private String output; 24 | 25 | public Challenge getChallenge() { 26 | return challenge; 27 | } 28 | 29 | public void setChallenge(Challenge challenge) { 30 | this.challenge = challenge; 31 | } 32 | 33 | public List getInputs() { 34 | return inputs; 35 | } 36 | 37 | public void setInputs(List inputs) { 38 | inputs.forEach(i -> i.setTestCase(this)); 39 | this.inputs = inputs; 40 | } 41 | 42 | public String getOutput() { 43 | return output; 44 | } 45 | 46 | public void setOutput(String output) { 47 | this.output = output; 48 | } 49 | 50 | public DataType getOutputType() { 51 | return getChallenge().getOutputType(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/challenge/TestCaseInputValue.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.challenge; 2 | 3 | import com.expercise.domain.PrioritizedEntity; 4 | import com.expercise.utils.Constants; 5 | 6 | import javax.persistence.*; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Entity 11 | @SequenceGenerator(name = "ID_GENERATOR", sequenceName = "SEQ_TEST_CASE_INPUT_VALUE") 12 | public class TestCaseInputValue extends PrioritizedEntity { 13 | 14 | @Column(nullable = false, length = Constants.MAX_TESTCASE_VALUE_LENGTH) 15 | private String inputValue; 16 | 17 | @ManyToOne(fetch = FetchType.LAZY, optional = false) 18 | private TestCase testCase; 19 | 20 | public static List createFrom(List inputValues) { 21 | List testCaseInputValues = new ArrayList<>(); 22 | for (String inputValue : inputValues) { 23 | TestCaseInputValue testCaseInputValue = new TestCaseInputValue(); 24 | testCaseInputValue.setInputValue(inputValue); 25 | testCaseInputValues.add(testCaseInputValue); 26 | } 27 | prioritize(testCaseInputValues); 28 | return testCaseInputValues; 29 | } 30 | 31 | public String getInputValue() { 32 | return inputValue; 33 | } 34 | 35 | public void setInputValue(String inputValue) { 36 | this.inputValue = inputValue; 37 | } 38 | 39 | public TestCase getTestCase() { 40 | return testCase; 41 | } 42 | 43 | public void setTestCase(TestCase testCase) { 44 | this.testCase = testCase; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/quote/Quote.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.quote; 2 | 3 | import com.expercise.domain.BaseEntity; 4 | import com.expercise.enums.Lingo; 5 | 6 | import javax.persistence.*; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | @Entity 11 | @SequenceGenerator(name = "ID_GENERATOR", sequenceName = "SEQ_QUOTE") 12 | public class Quote extends BaseEntity { 13 | 14 | @ElementCollection 15 | @MapKeyEnumerated(EnumType.STRING) 16 | @MapKeyColumn(name = "LINGO") 17 | @Column(name = "TEXT", nullable = false, length = 1024) 18 | private Map quoteInMultiLingo = new HashMap<>(); 19 | 20 | private String author; 21 | 22 | public Map getQuoteInMultiLingo() { 23 | return quoteInMultiLingo; 24 | } 25 | 26 | public void setQuoteInMultiLingo(Map quoteInMultiLingo) { 27 | this.quoteInMultiLingo = quoteInMultiLingo; 28 | } 29 | 30 | public String getQuoteFor(String lingoShortName) { 31 | Lingo lingo = Lingo.getLingo(lingoShortName).get(); 32 | return quoteInMultiLingo.get(lingo); 33 | } 34 | 35 | public String getAuthor() { 36 | return author; 37 | } 38 | 39 | public void setAuthor(String author) { 40 | this.author = author; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/token/Token.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.token; 2 | 3 | import com.expercise.domain.BaseEntity; 4 | import com.expercise.domain.user.User; 5 | 6 | import javax.persistence.*; 7 | 8 | @Entity 9 | @SequenceGenerator(name = "ID_GENERATOR", sequenceName = "SEQ_TOKEN") 10 | public class Token extends BaseEntity { 11 | 12 | @OneToOne(optional = false, fetch = FetchType.EAGER) 13 | private User user; 14 | 15 | @Column(nullable = false, length = 32) 16 | private String token; 17 | 18 | @Column(nullable = false) 19 | @Enumerated(EnumType.STRING) 20 | private TokenType tokenType; 21 | 22 | public User getUser() { 23 | return user; 24 | } 25 | 26 | public void setUser(User user) { 27 | this.user = user; 28 | } 29 | 30 | public String getToken() { 31 | return token; 32 | } 33 | 34 | public void setToken(String token) { 35 | this.token = token; 36 | } 37 | 38 | public TokenType getTokenType() { 39 | return tokenType; 40 | } 41 | 42 | public void setTokenType(TokenType tokenType) { 43 | this.tokenType = tokenType; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/token/TokenType.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.token; 2 | 3 | public enum TokenType { 4 | 5 | FORGOT_MY_PASSWORD 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/user/RememberMeToken.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.user; 2 | 3 | import com.expercise.domain.BaseEntity; 4 | 5 | import javax.persistence.*; 6 | import java.util.Date; 7 | 8 | @Entity 9 | @SequenceGenerator(name = "ID_GENERATOR", sequenceName = "SEQ_REMEMBER_ME_TOKEN") 10 | public class RememberMeToken extends BaseEntity { 11 | 12 | @Column(nullable = false) 13 | private String email; 14 | 15 | @Column(length = 64, unique = true, nullable = false) 16 | private String series; 17 | 18 | @Column(length = 64, nullable = false) 19 | private String token; 20 | 21 | @Column(nullable = false) 22 | @Temporal(TemporalType.TIMESTAMP) 23 | private Date lastUsedTime; 24 | 25 | public String getEmail() { 26 | return email; 27 | } 28 | 29 | public void setEmail(String email) { 30 | this.email = email; 31 | } 32 | 33 | public String getSeries() { 34 | return series; 35 | } 36 | 37 | public void setSeries(String series) { 38 | this.series = series; 39 | } 40 | 41 | public String getToken() { 42 | return token; 43 | } 44 | 45 | public void setToken(String token) { 46 | this.token = token; 47 | } 48 | 49 | public Date getLastUsedTime() { 50 | return lastUsedTime; 51 | } 52 | 53 | public void setLastUsedTime(Date lastUsedTime) { 54 | this.lastUsedTime = lastUsedTime; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/domain/user/SocialUserDetails.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.user; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.social.security.SocialUser; 5 | 6 | import java.util.Collection; 7 | 8 | public class SocialUserDetails extends SocialUser { 9 | 10 | private Long id; 11 | 12 | private String firstName; 13 | 14 | private String lastName; 15 | 16 | public SocialUserDetails(String username, String password, Collection authorities) { 17 | super(username, password, authorities); 18 | } 19 | 20 | public Long getId() { 21 | return id; 22 | } 23 | 24 | public void setId(Long id) { 25 | this.id = id; 26 | } 27 | 28 | public String getFirstName() { 29 | return firstName; 30 | } 31 | 32 | public void setFirstName(String firstName) { 33 | this.firstName = firstName; 34 | } 35 | 36 | public String getLastName() { 37 | return lastName; 38 | } 39 | 40 | public void setLastName(String lastName) { 41 | this.lastName = lastName; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/enums/ChallengeListingMode.java: -------------------------------------------------------------------------------- 1 | package com.expercise.enums; 2 | 3 | public enum ChallengeListingMode { 4 | 5 | Admin, 6 | User 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/enums/DataType.java: -------------------------------------------------------------------------------- 1 | package com.expercise.enums; 2 | 3 | import com.expercise.exception.ExperciseJsonException; 4 | import com.expercise.utils.JsonUtils; 5 | import org.apache.commons.lang3.StringEscapeUtils; 6 | 7 | import java.util.List; 8 | 9 | public enum DataType { 10 | 11 | Integer { 12 | @Override 13 | public Object toJavaObject(String rawValue) { 14 | return JsonUtils.fromJson(rawValue, Integer.class); 15 | } 16 | 17 | @Override 18 | public void validateJson(String jsonString) throws ExperciseJsonException { 19 | JsonUtils.formatSafely(jsonString, Integer.class); 20 | } 21 | }, 22 | 23 | Text { 24 | @Override 25 | public Object toJavaObject(String rawValue) { 26 | return JsonUtils.fromJson(rawValue, String.class); 27 | } 28 | 29 | @Override 30 | public void validateJson(String jsonString) throws ExperciseJsonException { 31 | JsonUtils.formatSafely(jsonString, String.class); 32 | } 33 | }, 34 | 35 | Array { 36 | @Override 37 | public Object toJavaObject(String rawValue) { 38 | return JsonUtils.fromJson(rawValue, List.class); 39 | } 40 | 41 | @Override 42 | public void validateJson(String jsonString) throws ExperciseJsonException { 43 | JsonUtils.formatSafely(jsonString, List.class); 44 | } 45 | }; 46 | 47 | public static String toLiteral(Object object) { 48 | if (object instanceof String) { 49 | return "\"" + StringEscapeUtils.escapeJava(object.toString()) + "\""; 50 | } else { 51 | return object.toString(); 52 | } 53 | } 54 | 55 | public abstract Object toJavaObject(String rawValue); 56 | 57 | public abstract void validateJson(String jsonString) throws ExperciseJsonException; 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/enums/ManagementMode.java: -------------------------------------------------------------------------------- 1 | package com.expercise.enums; 2 | 3 | public enum ManagementMode { 4 | 5 | Add, 6 | Update 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/enums/ProgrammingLanguage.java: -------------------------------------------------------------------------------- 1 | package com.expercise.enums; 2 | 3 | import java.util.Arrays; 4 | import java.util.Optional; 5 | 6 | public enum ProgrammingLanguage { 7 | 8 | JavaScript("js"), 9 | Python2("py2"), 10 | Python3("py3"), 11 | Java("java"); 12 | 13 | private String shortName; 14 | 15 | ProgrammingLanguage(String shortName) { 16 | this.shortName = shortName; 17 | } 18 | 19 | public static Optional getLanguage(String shortName) { 20 | return Arrays.asList(values()).stream() 21 | .filter(p -> p.getShortName().equals(shortName)) 22 | .findFirst(); 23 | } 24 | 25 | public String getShortName() { 26 | return shortName; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/enums/SocialSignInProvider.java: -------------------------------------------------------------------------------- 1 | package com.expercise.enums; 2 | 3 | import java.util.Optional; 4 | import java.util.stream.Stream; 5 | 6 | public enum SocialSignInProvider { 7 | 8 | Twitter("twitter"), 9 | Facebook("facebook"), 10 | LinkedIn("linkedin"), 11 | GitHub("github"), 12 | Google("google"); 13 | 14 | private final String providerId; 15 | 16 | SocialSignInProvider(String providerId) { 17 | this.providerId = providerId; 18 | } 19 | 20 | public static Optional getForProviderId(String providerId) { 21 | return Stream.of(values()) 22 | .filter(it -> it.providerId.equals(providerId)) 23 | .findFirst(); 24 | } 25 | 26 | public String getProviderId() { 27 | return providerId; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/enums/UserRole.java: -------------------------------------------------------------------------------- 1 | package com.expercise.enums; 2 | 3 | public enum UserRole { 4 | 5 | User("ROLE_USER"), 6 | Admin("ROLE_USER", "ROLE_ADMIN"); 7 | 8 | private String[] authorities; 9 | 10 | private UserRole(String... authorities) { 11 | this.authorities = authorities; 12 | } 13 | 14 | public String[] getAuthorities() { 15 | return authorities; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/exception/ExperciseGenericException.java: -------------------------------------------------------------------------------- 1 | package com.expercise.exception; 2 | 3 | public class ExperciseGenericException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -4059537275690891978L; 6 | 7 | public ExperciseGenericException(String message, Throwable exception) { 8 | super(message, exception); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/exception/ExperciseJsonException.java: -------------------------------------------------------------------------------- 1 | package com.expercise.exception; 2 | 3 | import java.io.IOException; 4 | 5 | public class ExperciseJsonException extends IOException { 6 | 7 | private static final long serialVersionUID = 5314405037914650141L; 8 | 9 | public ExperciseJsonException(String message, Throwable exception) { 10 | super(message, exception); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/InterpretRequest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | public class InterpretRequest { 4 | 5 | private final String sourceCode; 6 | private final String programmingLanguage; 7 | 8 | public InterpretRequest(String sourceCode, String programmingLanguage) { 9 | this.sourceCode = sourceCode; 10 | this.programmingLanguage = programmingLanguage; 11 | } 12 | 13 | public String getSourceCode() { 14 | return sourceCode; 15 | } 16 | 17 | public String getProgrammingLanguage() { 18 | return programmingLanguage; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/InterpretResponse.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | public class InterpretResponse { 4 | 5 | private String stdOut; 6 | private String stdErr; 7 | 8 | public String getStdOut() { 9 | return stdOut; 10 | } 11 | 12 | public void setStdOut(String stdOut) { 13 | this.stdOut = stdOut; 14 | } 15 | 16 | public String getStdErr() { 17 | return stdErr; 18 | } 19 | 20 | public void setStdErr(String stdErr) { 21 | this.stdErr = stdErr; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "InterpretResponse{" + 27 | "stdOut='" + stdOut + '\'' + 28 | ", stdErr='" + stdErr + '\'' + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/Interpreter.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 4 | 5 | import java.util.concurrent.*; 6 | 7 | public abstract class Interpreter { 8 | 9 | private static final int TIMEOUT_AS_SECONDS = 20; 10 | 11 | protected abstract void interpretInternal(ChallengeEvaluationContext context) throws InterpreterException; 12 | 13 | public final void interpret(ChallengeEvaluationContext context) { 14 | ExecutorService executor = Executors.newSingleThreadExecutor(); 15 | Future future = executor.submit(() -> { 16 | interpretInternal(context); 17 | return null; 18 | }); 19 | 20 | try { 21 | future.get(TIMEOUT_AS_SECONDS, TimeUnit.SECONDS); 22 | } catch (TimeoutException | InterruptedException | ExecutionException e) { 23 | Throwable cause = e.getCause(); 24 | if (cause instanceof InterpreterException) { 25 | context.setInterpreterResult(((InterpreterException) cause).getInterpreterResult()); 26 | } else { 27 | context.setInterpreterResult(InterpreterResult.noResultFailedResult()); 28 | } 29 | } finally { 30 | executor.shutdownNow(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/InterpreterClient.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | @Component 8 | public class InterpreterClient { 9 | 10 | @Value("${coding-expercise.interpreter.api.url}") 11 | private String interpreterApiUrl; 12 | 13 | public InterpretResponse interpret(InterpretRequest interpretRequest) { 14 | return new RestTemplate().postForEntity(interpreterApiUrl + "/eval", interpretRequest, InterpretResponse.class).getBody(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/InterpreterException.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | public class InterpreterException extends Exception { 4 | 5 | private static final long serialVersionUID = -9010793998200028098L; 6 | 7 | private final InterpreterResult interpreterResult; 8 | 9 | public InterpreterException(InterpreterResult interpreterResult) { 10 | this.interpreterResult = interpreterResult; 11 | } 12 | 13 | public InterpreterResult getInterpreterResult() { 14 | return interpreterResult; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/InterpreterFailureType.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | public enum InterpreterFailureType { 4 | 5 | NO_RESULT("interpreter.noResult"); 6 | 7 | private String messageKey; 8 | 9 | InterpreterFailureType(String messageKey) { 10 | this.messageKey = messageKey; 11 | } 12 | 13 | public String getMessageKey() { 14 | return messageKey; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/InterpreterResult.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | public class InterpreterResult { 4 | 5 | private InterpreterFailureType failureType; 6 | 7 | private boolean success; 8 | 9 | private InterpreterResult(boolean success) { 10 | this.success = success; 11 | } 12 | 13 | public static InterpreterResult createSuccessResult() { 14 | return new InterpreterResult(true); 15 | } 16 | 17 | public static InterpreterResult createFailedResult() { 18 | return new InterpreterResult(false); 19 | } 20 | 21 | public static InterpreterResult noResultFailedResult() { 22 | InterpreterResult failedResult = createFailedResult(); 23 | failedResult.setFailureType(InterpreterFailureType.NO_RESULT); 24 | return failedResult; 25 | } 26 | 27 | public InterpreterFailureType getFailureType() { 28 | return failureType; 29 | } 30 | 31 | public void setFailureType(InterpreterFailureType failureType) { 32 | this.failureType = failureType; 33 | } 34 | 35 | public boolean isSuccess() { 36 | return success; 37 | } 38 | 39 | public void setSuccess(boolean success) { 40 | this.success = success; 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/TestCaseModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | import com.expercise.domain.challenge.ChallengeInputType; 4 | import com.expercise.domain.challenge.TestCase; 5 | 6 | import java.io.Serializable; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class TestCaseModel implements Serializable { 11 | 12 | private static final long serialVersionUID = -5656146862340963922L; 13 | 14 | private List inputs = new ArrayList<>(); 15 | 16 | private String output; 17 | 18 | private String actualValue; 19 | 20 | private TestCaseResult testCaseResult; 21 | 22 | private String resultMessage; 23 | 24 | public TestCaseModel(TestCaseWithResult testCaseWithResult) { 25 | TestCase testCaseUnderTest = testCaseWithResult.getTestCaseUnderTest(); 26 | List inputTypes = testCaseUnderTest.getChallenge().getInputTypes(); 27 | for (int i = 0; i < inputTypes.size(); i++) { 28 | String inputValue = testCaseUnderTest.getInputs().get(i).getInputValue(); 29 | this.inputs.add(inputValue); 30 | } 31 | this.output = testCaseUnderTest.getOutput(); 32 | this.actualValue = testCaseWithResult.getActualValue(); 33 | this.testCaseResult = testCaseWithResult.getTestCaseResult(); 34 | this.resultMessage = testCaseWithResult.getResultMessage(); 35 | } 36 | 37 | public List getInputs() { 38 | return inputs; 39 | } 40 | 41 | public String getOutput() { 42 | return output; 43 | } 44 | 45 | public String getActualValue() { 46 | return actualValue; 47 | } 48 | 49 | public TestCaseResult getTestCaseResult() { 50 | return testCaseResult; 51 | } 52 | 53 | public String getResultMessage() { 54 | return resultMessage; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/TestCaseResult.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | public enum TestCaseResult { 4 | 5 | NEW, 6 | PASSED, 7 | FAILED 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/TestCaseWithResult.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | import com.expercise.domain.challenge.TestCase; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.io.Serializable; 7 | 8 | public class TestCaseWithResult implements Serializable { 9 | 10 | private static final long serialVersionUID = 3004304070142162027L; 11 | 12 | private TestCase testCaseUnderTest; 13 | 14 | private TestCaseResult testCaseResult; 15 | 16 | private String actualValue; 17 | 18 | private String resultMessage = StringUtils.EMPTY; 19 | 20 | public TestCaseWithResult(TestCase testCaseUnderTest) { 21 | this.testCaseUnderTest = testCaseUnderTest; 22 | this.testCaseResult = TestCaseResult.NEW; 23 | } 24 | 25 | public TestCase getTestCaseUnderTest() { 26 | return testCaseUnderTest; 27 | } 28 | 29 | public TestCaseResult getTestCaseResult() { 30 | return testCaseResult; 31 | } 32 | 33 | public void setTestCaseResult(TestCaseResult testCaseResult) { 34 | this.testCaseResult = testCaseResult; 35 | } 36 | 37 | public String getExpectedValue() { 38 | return getTestCaseUnderTest().getOutput(); 39 | } 40 | 41 | public String getActualValue() { 42 | return actualValue; 43 | } 44 | 45 | public void setActualValue(String actualValue) { 46 | this.actualValue = actualValue; 47 | } 48 | 49 | public String getResultMessage() { 50 | return resultMessage; 51 | } 52 | 53 | public void setResultMessage(String resultMessage) { 54 | this.resultMessage = resultMessage; 55 | } 56 | 57 | public boolean isFailed() { 58 | return getTestCaseResult() == TestCaseResult.FAILED; 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/TestCasesWithSourceCacheModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.io.Serializable; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class TestCasesWithSourceCacheModel implements Serializable { 10 | 11 | private static final long serialVersionUID = -423521965916958053L; 12 | 13 | private String currentSourceCode = StringUtils.EMPTY; 14 | 15 | private List testCaseResults = new ArrayList<>(); 16 | 17 | public TestCasesWithSourceCacheModel(List testCaseResults) { 18 | this(StringUtils.EMPTY, testCaseResults); 19 | } 20 | 21 | public TestCasesWithSourceCacheModel(String currentSourceCode, List testCaseResults) { 22 | this.currentSourceCode = currentSourceCode; 23 | this.testCaseResults = testCaseResults; 24 | } 25 | 26 | public void clear() { 27 | this.currentSourceCode = StringUtils.EMPTY; 28 | this.testCaseResults.clear(); 29 | } 30 | 31 | public void reset() { 32 | this.currentSourceCode = StringUtils.EMPTY; 33 | this.testCaseResults.forEach(t -> { 34 | t.setTestCaseResult(TestCaseResult.NEW); 35 | t.setActualValue(null); 36 | t.setResultMessage(StringUtils.EMPTY); 37 | }); 38 | } 39 | 40 | public String getCurrentSourceCode() { 41 | return currentSourceCode; 42 | } 43 | 44 | public void setCurrentSourceCode(String currentSourceCode) { 45 | this.currentSourceCode = currentSourceCode; 46 | } 47 | 48 | public List getTestCaseResults() { 49 | return testCaseResults; 50 | } 51 | 52 | public void setTestCaseResults(List testCaseResults) { 53 | this.testCaseResults = testCaseResults; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/interpreter/TestCasesWithSourceModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.io.Serializable; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | public class TestCasesWithSourceModel implements Serializable { 11 | 12 | private static final long serialVersionUID = 5558397147480824685L; 13 | 14 | private String currentSourceCode = StringUtils.EMPTY; 15 | 16 | private List testCaseModels = new ArrayList<>(); 17 | 18 | public TestCasesWithSourceModel() { 19 | } 20 | 21 | public TestCasesWithSourceModel(TestCasesWithSourceCacheModel testCasesWithSourceCacheModel) { 22 | currentSourceCode = testCasesWithSourceCacheModel.getCurrentSourceCode(); 23 | testCaseModels = testCasesWithSourceCacheModel.getTestCaseResults() 24 | .stream() 25 | .map(TestCaseModel::new) 26 | .collect(Collectors.toList()); 27 | } 28 | 29 | public String getCurrentSourceCode() { 30 | return currentSourceCode; 31 | } 32 | 33 | public void setCurrentSourceCode(String currentSourceCode) { 34 | this.currentSourceCode = currentSourceCode; 35 | } 36 | 37 | public List getTestCaseModels() { 38 | return testCaseModels; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/BaseRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository; 2 | 3 | import com.expercise.domain.BaseEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.repository.NoRepositoryBean; 6 | 7 | @NoRepositoryBean 8 | public interface BaseRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/challenge/ChallengeRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository.challenge; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.user.User; 5 | import com.expercise.repository.BaseRepository; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import java.util.List; 10 | 11 | public interface ChallengeRepository extends BaseRepository { 12 | 13 | List findByApprovedIsTrue(); 14 | 15 | List findByUserOrderByCreateDateDesc(User user); 16 | 17 | List findAllByOrderByCreateDateDesc(); 18 | 19 | List findByApprovedIsTrueAndUser(User user); 20 | 21 | @Query("select c.id, c.tags from Challenge c where c.approved = true order by c.createDate desc") 22 | List getChallengeWithIdAndTags(Pageable pageable); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/challenge/SolutionRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository.challenge; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.Solution; 5 | import com.expercise.domain.user.User; 6 | import com.expercise.enums.ProgrammingLanguage; 7 | import com.expercise.repository.BaseRepository; 8 | 9 | import java.util.List; 10 | 11 | public interface SolutionRepository extends BaseRepository { 12 | 13 | Solution findByChallengeAndUserAndProgrammingLanguage(Challenge challenge, User user, ProgrammingLanguage programmingLanguage); 14 | 15 | List findByChallengeApprovedIsTrueAndUser(User user); 16 | 17 | List findByChallengeAndUser(Challenge challenge, User user); 18 | 19 | Long countByChallenge(Challenge challenge); 20 | 21 | Long countByUser(User user); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/challenge/UserPointRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository.challenge; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.UserPoint; 5 | import com.expercise.domain.user.User; 6 | import com.expercise.enums.ProgrammingLanguage; 7 | import com.expercise.repository.BaseRepository; 8 | import org.springframework.data.jpa.repository.Query; 9 | 10 | public interface UserPointRepository extends BaseRepository { 11 | 12 | UserPoint findByChallengeAndUser(Challenge challenge, User user); 13 | 14 | Long countByChallengeAndUserAndProgrammingLanguage(Challenge challenge, User user, ProgrammingLanguage programmingLanguage); 15 | 16 | @Query("select sum(up.pointAmount) from UserPoint up where up.user.id = ?1") 17 | Long getTotalPointsOf(Long userId); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/quote/QuoteRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository.quote; 2 | 3 | import com.expercise.domain.quote.Quote; 4 | import com.expercise.repository.BaseRepository; 5 | 6 | public interface QuoteRepository extends BaseRepository { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/user/RememberMeTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository.user; 2 | 3 | import com.expercise.domain.user.RememberMeToken; 4 | import com.expercise.repository.BaseRepository; 5 | import org.springframework.data.jpa.repository.Modifying; 6 | 7 | public interface RememberMeTokenRepository extends BaseRepository { 8 | 9 | RememberMeToken findBySeries(String series); 10 | 11 | @Modifying 12 | void deleteByEmail(String email); 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/user/TokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository.user; 2 | 3 | import com.expercise.domain.token.Token; 4 | import com.expercise.domain.token.TokenType; 5 | import com.expercise.domain.user.User; 6 | import com.expercise.repository.BaseRepository; 7 | import org.springframework.data.jpa.repository.Modifying; 8 | 9 | public interface TokenRepository extends BaseRepository { 10 | 11 | Token findByTokenAndTokenType(String token, TokenType tokenType); 12 | 13 | @Modifying 14 | void deleteByUser(User user); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/user/UserConnectionRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository.user; 2 | 3 | import com.expercise.domain.user.UserConnection; 4 | import com.expercise.repository.BaseRepository; 5 | 6 | public interface UserConnectionRepository extends BaseRepository { 7 | 8 | UserConnection findByProviderIdAndProviderUserId(String providerId, String providerUserId); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/repository/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.expercise.repository.user; 2 | 3 | import com.expercise.domain.user.User; 4 | import com.expercise.repository.BaseRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.List; 8 | 9 | public interface UserRepository extends BaseRepository { 10 | 11 | User findByEmail(String email); 12 | 13 | @Query("select u.id from User u") 14 | List findAllIds(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/cache/JsonRedisTemplate.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.cache; 2 | 3 | import org.springframework.data.redis.core.RedisTemplate; 4 | 5 | public final class JsonRedisTemplate extends RedisTemplate { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/cache/ObjectRedisTemplate.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.cache; 2 | 3 | import org.springframework.data.redis.core.RedisTemplate; 4 | 5 | import java.io.Serializable; 6 | 7 | public final class ObjectRedisTemplate extends RedisTemplate { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/ChallengeDisplayRule.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.user.User; 5 | import com.expercise.service.user.AuthenticationService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class ChallengeDisplayRule { 11 | 12 | @Autowired 13 | private AuthenticationService authenticationService; 14 | 15 | public boolean isNotDisplayable(Challenge challenge) { 16 | if (!authenticationService.isCurrentUserAuthenticated() && challenge.isApproved()) { 17 | return false; 18 | } 19 | 20 | User currentUser = authenticationService.getCurrentUser(); 21 | 22 | if (currentUser.isAdmin()) { 23 | return false; 24 | } 25 | 26 | if (challenge.isNotApproved()) { 27 | return challenge.isNotAuthor(currentUser); 28 | } 29 | 30 | return false; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/LeaderBoardCalculatorService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge; 2 | 3 | import com.expercise.service.cache.RedisCacheService; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | 13 | @Service 14 | public class LeaderBoardCalculatorService { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(LeaderBoardCalculatorService.class); 17 | 18 | private ExecutorService executorService = Executors.newFixedThreadPool(1); 19 | 20 | @Autowired 21 | private RedisCacheService redisCacheService; 22 | 23 | @Autowired 24 | private LeaderBoardService leaderBoardService; 25 | 26 | @PostConstruct 27 | public void init() { 28 | listenQueue(); 29 | } 30 | 31 | public void listenQueue() { 32 | executorService.submit(() -> { 33 | while (true) { 34 | try { 35 | Long userId = redisCacheService.leftPop(UserPointService.LEADERBOARD_QUEUE); 36 | if (userId != null) { 37 | leaderBoardService.updateLeaderBoardPoint(userId); 38 | } 39 | Thread.sleep(500); 40 | } catch (Exception e) { 41 | LOGGER.error("An error occurred while calculating points", e); 42 | } 43 | } 44 | }); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/LeaderBoardWarmUpUtil.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge; 2 | 3 | import com.expercise.service.user.UserService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.transaction.PlatformTransactionManager; 7 | import org.springframework.transaction.support.TransactionTemplate; 8 | 9 | import javax.annotation.PostConstruct; 10 | 11 | @Component 12 | public class LeaderBoardWarmUpUtil { 13 | 14 | @Autowired 15 | private LeaderBoardService leaderBoardService; 16 | 17 | @Autowired 18 | private UserService userService; 19 | 20 | @Autowired 21 | private PlatformTransactionManager transactionManager; 22 | 23 | @PostConstruct 24 | public void init() { 25 | warmUpLeaderBoard(); 26 | } 27 | 28 | private void warmUpLeaderBoard() { 29 | new Thread(() -> { 30 | new TransactionTemplate(transactionManager).execute(status -> { 31 | userService.findAllIds() 32 | .forEach(id -> leaderBoardService.updateLeaderBoardPoint(id)); 33 | 34 | return null; 35 | }); 36 | }).start(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/SolutionCountService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge; 2 | 3 | import com.expercise.utils.caching.Caching; 4 | import com.expercise.domain.challenge.Challenge; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | @Service 14 | public class SolutionCountService implements Caching { 15 | 16 | private static final Map SOLUTION_COUNTS = new ConcurrentHashMap<>(); 17 | 18 | @Autowired 19 | private SolutionService solutionService; 20 | 21 | public Long getSolutionCountOf(Challenge challenge) { 22 | Long challengeId = challenge.getId(); 23 | Long solutionCount = SOLUTION_COUNTS.get(challengeId); 24 | if (solutionCount == null) { 25 | solutionCount = solutionService.getSolutionCountOf(challenge); 26 | SOLUTION_COUNTS.put(challengeId, solutionCount); 27 | } 28 | return solutionCount; 29 | } 30 | 31 | public Map prepareSolutionCountMapFor(List challenges) { 32 | Map solutionCountMap = new HashMap<>(); 33 | for (Challenge eachChallenge : challenges) { 34 | solutionCountMap.put(eachChallenge, getSolutionCountOf(eachChallenge)); 35 | } 36 | return solutionCountMap; 37 | } 38 | 39 | public void clearCacheFor(Long challengeId) { 40 | SOLUTION_COUNTS.remove(challengeId); 41 | } 42 | 43 | @Override 44 | public void flush() { 45 | SOLUTION_COUNTS.clear(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/action/PostEvaluationAction.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action; 2 | 3 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 4 | import com.expercise.service.util.Prioritized; 5 | 6 | public interface PostEvaluationAction extends Prioritized { 7 | 8 | boolean canExecute(ChallengeEvaluationContext context); 9 | 10 | void execute(ChallengeEvaluationContext context); 11 | 12 | enum PostEvaluationActionOrder { 13 | 14 | INTERPRETATION_FAILURE_CHECK_FOR_TEST_CASES, 15 | SAVE_USER_TEST_CASE_STATE, 16 | CREATE_SOLUTION_RESPONSE, 17 | CREATE_KATA_SOLUTION_RESPONSE, 18 | GIVE_SUCCESS_POINT, 19 | SAVE_USER_SOLUTION, 20 | PREPARE_CHALLENGE_USER_SOLUTIONS, 21 | NOTIFY_CHALLENGE_COMPLETED 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/action/PostEvaluationExecutor.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action; 2 | 3 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 4 | import com.expercise.service.util.PrioritySorter; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Service 12 | public class PostEvaluationExecutor { 13 | 14 | @Autowired(required = false) 15 | private List postEvaluationActions = new ArrayList<>(); 16 | 17 | public void execute(ChallengeEvaluationContext context) { 18 | postEvaluationActions.stream() 19 | .filter(action -> action.canExecute(context)) 20 | .sorted(new PrioritySorter()) 21 | .forEach(a -> a.execute(context)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/action/PreEvaluationAction.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action; 2 | 3 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 4 | 5 | public interface PreEvaluationAction { 6 | 7 | void execute(ChallengeEvaluationContext context); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/action/PreEvaluationExecutor.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action; 2 | 3 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Service 11 | public class PreEvaluationExecutor { 12 | 13 | @Autowired(required = false) 14 | private List preEvaluationActions = new ArrayList<>(); 15 | 16 | public void execute(ChallengeEvaluationContext context) { 17 | preEvaluationActions.forEach(a -> a.execute(context)); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/action/postaction/GiveSuccessPointPostAction.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action.postaction; 2 | 3 | import com.expercise.service.challenge.UserPointService; 4 | import com.expercise.service.challenge.action.PostEvaluationAction; 5 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 6 | import com.expercise.service.user.AuthenticationService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class GiveSuccessPointPostAction implements PostEvaluationAction { 12 | 13 | @Autowired 14 | private UserPointService userPointService; 15 | 16 | @Autowired 17 | private AuthenticationService authenticationService; 18 | 19 | @Override 20 | public boolean canExecute(ChallengeEvaluationContext context) { 21 | if (!authenticationService.isCurrentUserAuthenticated()) { 22 | return false; 23 | } 24 | 25 | return context.isChallengeCompleted() && userPointService.canUserWinPoint(context.getChallenge(), context.getLanguage()); 26 | } 27 | 28 | @Override 29 | public void execute(ChallengeEvaluationContext context) { 30 | userPointService.givePoint(context.getChallenge(), authenticationService.getCurrentUser(), context.getLanguage()); 31 | } 32 | 33 | @Override 34 | public int getPriority() { 35 | return PostEvaluationActionOrder.GIVE_SUCCESS_POINT.ordinal(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/action/postaction/InterpretationFailureCheckForTestCasesPostAction.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action.postaction; 2 | 3 | import com.expercise.interpreter.InterpreterResult; 4 | import com.expercise.interpreter.TestCaseResult; 5 | import com.expercise.interpreter.TestCaseWithResult; 6 | import com.expercise.service.challenge.action.PostEvaluationAction; 7 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class InterpretationFailureCheckForTestCasesPostAction implements PostEvaluationAction { 12 | 13 | @Override 14 | public boolean canExecute(ChallengeEvaluationContext context) { 15 | InterpreterResult interpreterResult = context.getInterpreterResult(); 16 | return !interpreterResult.isSuccess() && failedByLanguageSpecificError(interpreterResult); 17 | } 18 | 19 | @Override 20 | public void execute(ChallengeEvaluationContext context) { 21 | for (TestCaseWithResult each : context.getTestCaseWithResults()) { 22 | each.setTestCaseResult(TestCaseResult.FAILED); 23 | } 24 | } 25 | 26 | private boolean failedByLanguageSpecificError(InterpreterResult interpreterResult) { 27 | return interpreterResult.getFailureType() != null; 28 | } 29 | 30 | @Override 31 | public int getPriority() { 32 | return PostEvaluationActionOrder.INTERPRETATION_FAILURE_CHECK_FOR_TEST_CASES.ordinal(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/action/postaction/PrepareChallengeUserSolutionsPostAction.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action.postaction; 2 | 3 | import com.expercise.controller.challenge.model.UserSolutionModel; 4 | import com.expercise.service.challenge.SolutionService; 5 | import com.expercise.service.challenge.action.PostEvaluationAction; 6 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 7 | import com.expercise.service.user.AuthenticationService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | 13 | @Service 14 | public class PrepareChallengeUserSolutionsPostAction implements PostEvaluationAction { 15 | 16 | @Autowired 17 | private SolutionService solutionService; 18 | 19 | @Autowired 20 | private AuthenticationService authenticationService; 21 | 22 | @Override 23 | public boolean canExecute(ChallengeEvaluationContext context) { 24 | return authenticationService.isCurrentUserAuthenticated() && context.isChallengeCompleted(); 25 | } 26 | 27 | @Override 28 | public void execute(ChallengeEvaluationContext context) { 29 | List userSolutionModels = solutionService.getUserSolutionModels(context.getChallenge()); 30 | context.getSolutionValidationResult().setUserSolutionModels(userSolutionModels); 31 | } 32 | 33 | @Override 34 | public int getPriority() { 35 | return PostEvaluationActionOrder.PREPARE_CHALLENGE_USER_SOLUTIONS.ordinal(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/action/postaction/SaveUserTestCaseStatePostAction.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action.postaction; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.ChallengeType; 5 | import com.expercise.interpreter.TestCaseWithResult; 6 | import com.expercise.service.challenge.UserTestCaseStateService; 7 | import com.expercise.service.challenge.action.PostEvaluationAction; 8 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.List; 13 | 14 | @Service 15 | public class SaveUserTestCaseStatePostAction implements PostEvaluationAction { 16 | 17 | @Autowired 18 | private UserTestCaseStateService userTestCaseStateService; 19 | 20 | @Override 21 | public boolean canExecute(ChallengeEvaluationContext context) { 22 | return context.getChallenge().getChallengeType() == ChallengeType.CODE_KATA; 23 | } 24 | 25 | @Override 26 | public void execute(ChallengeEvaluationContext context) { 27 | Challenge challenge = context.getChallenge(); 28 | List testCaseWithResults = context.getTestCaseWithResults(); 29 | userTestCaseStateService.saveUserState(challenge, context.getSource(), context.getLanguage(), testCaseWithResults); 30 | } 31 | 32 | @Override 33 | public int getPriority() { 34 | return PostEvaluationActionOrder.SAVE_USER_TEST_CASE_STATE.ordinal(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/model/ChallengeSolutionStatus.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.model; 2 | 3 | public enum ChallengeSolutionStatus { 4 | 5 | FAILED, 6 | TESTS_PASSED_BUT_NOT_COMPLETED_YET, 7 | CHALLENGE_COMPLETED 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/challenge/model/LeaderBoardModel.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.model; 2 | 3 | import com.expercise.domain.user.User; 4 | 5 | import java.io.Serializable; 6 | 7 | public class LeaderBoardModel implements Serializable { 8 | 9 | private User user; 10 | 11 | private Integer point; 12 | 13 | public LeaderBoardModel() { 14 | } 15 | 16 | public LeaderBoardModel(User user, Integer point) { 17 | this.user = user; 18 | this.point = point; 19 | } 20 | 21 | public User getUser() { 22 | return user; 23 | } 24 | 25 | public void setUser(User user) { 26 | this.user = user; 27 | } 28 | 29 | public Integer getPoint() { 30 | return point; 31 | } 32 | 33 | public void setPoint(Integer point) { 34 | this.point = point; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/configuration/Configurations.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.configuration; 2 | 3 | import com.expercise.utils.EnvironmentUtils; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class Configurations { 9 | 10 | @Value("${spring.profiles.active}") 11 | private String environment; 12 | 13 | @Value("${coding-expercise.googleAnalytics.applicationKey}") 14 | private String googleAnalyticsApplicationKey; 15 | 16 | @Value("${coding-expercise.slack.incomingWebhookUrl}") 17 | private String slackIncomingWebhookUrl; 18 | 19 | @Value("${coding-expercise.defaults.challengeId}") 20 | private Long defaultChallengeId; 21 | 22 | @Value("${coding-expercise.challenge-approval-strategy}") 23 | private String challengeApprovalStrategy; 24 | 25 | public String getGoogleAnalyticsApplicationKey() { 26 | return googleAnalyticsApplicationKey; 27 | } 28 | 29 | public String getSlackIncomingWebhookUrl() { 30 | return slackIncomingWebhookUrl; 31 | } 32 | 33 | public Long getDefaultChallengeId() { 34 | return defaultChallengeId; 35 | } 36 | 37 | public boolean isDevelopment() { 38 | return EnvironmentUtils.isDevelopment(environment); 39 | } 40 | 41 | public String getChallengeApprovalStrategy() { 42 | return challengeApprovalStrategy; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/email/EmailSenderService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.email; 2 | 3 | import com.expercise.service.email.model.Email; 4 | 5 | public interface EmailSenderService { 6 | 7 | void send(Email email); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/email/EmailService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.email; 2 | 3 | import com.expercise.service.email.model.Email; 4 | import com.expercise.service.i18n.MessageService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.Map; 10 | 11 | @Service 12 | public class EmailService { 13 | 14 | @Autowired 15 | private MessageService messageService; 16 | 17 | @Autowired 18 | private EmailTemplateProcessor emailTemplateProcessor; 19 | 20 | @Autowired 21 | private EmailSenderService emailSenderService; 22 | 23 | @Value("${coding-expercise.email-status}") 24 | private String emailStatus; 25 | 26 | public void send(Email email, Map params) { 27 | if ("deactive".equals(emailStatus)) { 28 | return; 29 | } 30 | email.setSubject(messageService.getMessageForEmail(email.getSubjectKey())); 31 | email.setContent(emailTemplateProcessor.createEmail(email.getTemplateName(), params)); 32 | emailSenderService.send(email); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/email/EmailTemplateProcessor.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.email; 2 | 3 | import com.expercise.service.i18n.MessageService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | import org.thymeleaf.context.Context; 7 | 8 | import java.util.Map; 9 | 10 | @Service 11 | public class EmailTemplateProcessor { 12 | 13 | @Autowired 14 | private TemplateEngineWrapper templateEngineWrapper; 15 | 16 | @Autowired 17 | private MessageService messageService; 18 | 19 | public String createEmail(String emailTemplateName, Map params) { 20 | Context context = new Context(); 21 | params.entrySet().stream().forEach(it -> context.setVariable(it.getKey(), it.getValue())); 22 | context.setVariable("msg", messageService); 23 | return templateEngineWrapper.process(emailTemplateName, context); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/email/SendGridEmailSenderService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.email; 2 | 3 | import com.expercise.service.email.model.Email; 4 | import com.sendgrid.SendGrid; 5 | import com.sendgrid.SendGridException; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Service; 10 | 11 | import javax.annotation.PostConstruct; 12 | 13 | @Service 14 | public class SendGridEmailSenderService implements EmailSenderService { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(SendGridEmailSenderService.class); 17 | 18 | @Value("${coding-expercise.send-grid.username}") 19 | private String username; 20 | 21 | @Value("${coding-expercise.send-grid.password}") 22 | private String password; 23 | 24 | private SendGrid sendGridClient; 25 | 26 | @PostConstruct 27 | public void init() { 28 | sendGridClient = new SendGrid(username, password); 29 | } 30 | 31 | @Override 32 | public void send(Email email) { 33 | SendGrid.Email sendGridEmail = prepareSendGridEmailFrom(email); 34 | sendEmail(sendGridEmail); 35 | } 36 | 37 | private SendGrid.Email prepareSendGridEmailFrom(Email email) { 38 | return new SendGrid.Email() 39 | .addTo(email.getTo()) 40 | .setFrom(email.getFrom()) 41 | .setSubject(email.getSubject()) 42 | .setHtml(email.getContent()); 43 | } 44 | 45 | private void sendEmail(SendGrid.Email sendGridEmail) { 46 | try { 47 | SendGrid.Response response = sendGridClient.send(sendGridEmail); 48 | LOGGER.info(response.getMessage()); 49 | } catch (SendGridException e) { 50 | LOGGER.error("Exception while sending email via SendGrid.", e); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/email/TemplateEngineWrapper.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.email; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import org.thymeleaf.TemplateEngine; 6 | import org.thymeleaf.context.Context; 7 | 8 | @Service 9 | public class TemplateEngineWrapper { 10 | 11 | @Autowired 12 | private TemplateEngine templateEngine; 13 | 14 | public String process(String templateName, Context context) { 15 | return templateEngine.process(templateName, context); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/email/model/Email.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.email.model; 2 | 3 | public class Email { 4 | 5 | private String to; 6 | 7 | private String from; 8 | 9 | private String subject; 10 | 11 | private String content; 12 | 13 | private String subjectKey; 14 | 15 | private String templateName; 16 | 17 | public String getTo() { 18 | return to; 19 | } 20 | 21 | public Email setTo(String to) { 22 | this.to = to; 23 | return this; 24 | } 25 | 26 | public String getFrom() { 27 | return from; 28 | } 29 | 30 | public Email setFrom(String from) { 31 | this.from = from; 32 | return this; 33 | } 34 | 35 | public String getSubject() { 36 | return subject; 37 | } 38 | 39 | public Email setSubject(String subject) { 40 | this.subject = subject; 41 | return this; 42 | } 43 | 44 | public String getContent() { 45 | return content; 46 | } 47 | 48 | public Email setContent(String content) { 49 | this.content = content; 50 | return this; 51 | } 52 | 53 | public String getSubjectKey() { 54 | return subjectKey; 55 | } 56 | 57 | public Email setSubjectKey(String subjectKey) { 58 | this.subjectKey = subjectKey; 59 | return this; 60 | } 61 | 62 | public String getTemplateName() { 63 | return templateName; 64 | } 65 | 66 | public Email setTemplateName(String templateName) { 67 | this.templateName = templateName; 68 | return this; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/language/JavaScriptSignatureGenerator.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.language; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.enums.Lingo; 5 | import com.expercise.enums.ProgrammingLanguage; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | @Component 14 | public class JavaScriptSignatureGenerator implements SignatureGenerator { 15 | 16 | private static final String SIGNATURE_PATTERN = "function solution({params}) {\n\t\n}"; 17 | 18 | @Override 19 | public boolean canGenerateFor(ProgrammingLanguage language) { 20 | return ProgrammingLanguage.JavaScript == language; 21 | } 22 | 23 | @Override 24 | public String generate(Challenge challenge) { 25 | String customSignature = Lingo.getValueFrom(challenge.getSignatures()); 26 | if (StringUtils.isNotBlank(customSignature)) { 27 | return customSignature; 28 | } 29 | return generateFunctionCallSignature(challenge); 30 | } 31 | 32 | private String generateFunctionCallSignature(Challenge challenge) { 33 | List params = new ArrayList<>(); 34 | 35 | params.addAll(Arrays.asList(LETTERS).subList(0, challenge.getInputTypes().size())); 36 | 37 | return SIGNATURE_PATTERN.replace("{params}", String.join(", ", params)); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/language/Python2SignatureGenerator.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.language; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.enums.ProgrammingLanguage; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | @Component 12 | public class Python2SignatureGenerator implements SignatureGenerator { 13 | 14 | private static final String SIGNATURE_PATTERN = "def solution({params}):\n\t"; 15 | 16 | @Override 17 | public boolean canGenerateFor(ProgrammingLanguage language) { 18 | return ProgrammingLanguage.Python2 == language; 19 | } 20 | 21 | @Override 22 | public String generate(Challenge challenge) { 23 | return generateFunctionCallSignature(challenge); 24 | } 25 | 26 | private String generateFunctionCallSignature(Challenge challenge) { 27 | List params = new ArrayList<>(); 28 | 29 | params.addAll(Arrays.asList(LETTERS).subList(0, challenge.getInputTypes().size())); 30 | 31 | return SIGNATURE_PATTERN.replace("{params}", String.join(", ", params)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/language/Python3SignatureGenerator.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.language; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.enums.ProgrammingLanguage; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | @Component 12 | public class Python3SignatureGenerator implements SignatureGenerator { 13 | 14 | private static final String SIGNATURE_PATTERN = "def solution({params}):\n\t"; 15 | 16 | @Override 17 | public boolean canGenerateFor(ProgrammingLanguage language) { 18 | return ProgrammingLanguage.Python3 == language; 19 | } 20 | 21 | @Override 22 | public String generate(Challenge challenge) { 23 | return generateFunctionCallSignature(challenge); 24 | } 25 | 26 | private String generateFunctionCallSignature(Challenge challenge) { 27 | List params = new ArrayList<>(); 28 | 29 | params.addAll(Arrays.asList(LETTERS).subList(0, challenge.getInputTypes().size())); 30 | 31 | return SIGNATURE_PATTERN.replace("{params}", String.join(", ", params)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/language/SignatureGenerator.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.language; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.enums.ProgrammingLanguage; 5 | 6 | public interface SignatureGenerator { 7 | 8 | String[] LETTERS = new String[]{"a", "b", "c", "d", "e", "f", "g", "h"}; 9 | 10 | boolean canGenerateFor(ProgrammingLanguage language); 11 | 12 | String generate(Challenge challenge); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/language/SignatureGeneratorService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.language; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.enums.ProgrammingLanguage; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Service 12 | public class SignatureGeneratorService { 13 | 14 | @Autowired(required = false) 15 | private List signatureGenerators = new ArrayList<>(); 16 | 17 | public String generatorSignatureFor(Challenge challenge, ProgrammingLanguage language) { 18 | return findSignatureGeneratorFor(language).generate(challenge); 19 | } 20 | 21 | private SignatureGenerator findSignatureGeneratorFor(ProgrammingLanguage language) { 22 | return signatureGenerators.stream() 23 | .filter(g -> g.canGenerateFor(language)) 24 | .findFirst() 25 | .orElseThrow(() -> new IllegalArgumentException("Unsupported programming language: " + language)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/notification/SlackMessage.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.notification; 2 | 3 | public class SlackMessage { 4 | 5 | private String channel; 6 | 7 | private String text; 8 | 9 | public String getChannel() { 10 | return channel; 11 | } 12 | 13 | public void setChannel(String channel) { 14 | this.channel = channel; 15 | } 16 | 17 | public String getText() { 18 | return text; 19 | } 20 | 21 | public void setText(String text) { 22 | this.text = text; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "SlackMessage{" + 28 | "channel='" + channel + '\'' + 29 | ", text='" + text + '\'' + 30 | '}'; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/notification/SlackNotificationService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.notification; 2 | 3 | import com.expercise.service.configuration.Configurations; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpEntity; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.scheduling.annotation.Async; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.web.client.RestTemplate; 14 | 15 | @Service 16 | public class SlackNotificationService { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(SlackNotificationService.class); 19 | 20 | @Autowired 21 | private Configurations configurations; 22 | 23 | @Async 24 | public void sendMessage(SlackMessage slackMessage) { 25 | String slackIncomingWebhookUrl = configurations.getSlackIncomingWebhookUrl(); 26 | 27 | if (StringUtils.isBlank(slackIncomingWebhookUrl)) { 28 | LOGGER.warn("No slack incoming webhook url configured!"); 29 | return; 30 | } 31 | 32 | try { 33 | HttpHeaders httpHeaders = new HttpHeaders(); 34 | httpHeaders.add("Content-type", MediaType.APPLICATION_JSON_VALUE + "; charset=utf-8"); 35 | 36 | HttpEntity httpEntity = new HttpEntity<>(slackMessage, httpHeaders); 37 | 38 | new RestTemplate().postForLocation(slackIncomingWebhookUrl, httpEntity); 39 | } catch (Exception e) { 40 | LOGGER.error("Slack notification couldn't sent: " + slackMessage, e); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/quote/QuoteService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.quote; 2 | 3 | import com.expercise.utils.caching.Caching; 4 | import com.expercise.domain.quote.Quote; 5 | import com.expercise.repository.quote.QuoteRepository; 6 | import com.expercise.utils.collection.RandomElement; 7 | import org.hibernate.Hibernate; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.PlatformTransactionManager; 11 | import org.springframework.transaction.support.TransactionTemplate; 12 | 13 | import javax.annotation.PostConstruct; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | @Service 18 | public class QuoteService implements Caching { 19 | 20 | private static List QUOTES = new ArrayList<>(); 21 | 22 | @Autowired 23 | private PlatformTransactionManager transactionManager; 24 | 25 | @Autowired 26 | private QuoteRepository quoteRepository; 27 | 28 | @PostConstruct 29 | public void init() { 30 | populateQuoteList(); 31 | } 32 | 33 | public Quote randomQuote() { 34 | return RandomElement.from(QUOTES); 35 | } 36 | 37 | private void populateQuoteList() { 38 | new TransactionTemplate(transactionManager).execute(status -> { 39 | List freshQuotes = quoteRepository.findAll(); 40 | freshQuotes.forEach(q -> Hibernate.initialize(q.getQuoteInMultiLingo())); 41 | QUOTES = freshQuotes; 42 | return null; 43 | }); 44 | } 45 | 46 | @Override 47 | public void flush() { 48 | populateQuoteList(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/user/CurrentUserHolder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.user; 2 | 3 | import com.expercise.domain.user.User; 4 | import org.springframework.context.annotation.Scope; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @Scope("request") 9 | public class CurrentUserHolder { 10 | 11 | private User currentUser; 12 | 13 | public boolean hasNotCurrentUser() { 14 | return currentUser == null; 15 | } 16 | 17 | public User getCurrentUser() { 18 | return currentUser; 19 | } 20 | 21 | public void setCurrentUser(User currentUser) { 22 | this.currentUser = currentUser; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/user/PersistentRememberMeTokenRepositoryService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.user; 2 | 3 | import com.expercise.domain.user.RememberMeToken; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; 6 | import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.util.Date; 10 | 11 | public class PersistentRememberMeTokenRepositoryService implements PersistentTokenRepository { 12 | 13 | @Autowired 14 | private UserService userService; 15 | 16 | @Transactional 17 | @Override 18 | public void createNewToken(PersistentRememberMeToken token) { 19 | userService.saveRememberMeToken(token.getUsername(), token.getTokenValue(), token.getSeries(), token.getDate()); 20 | } 21 | 22 | @Transactional 23 | @Override 24 | public void updateToken(String series, String tokenValue, Date lastUsed) { 25 | userService.updateRememberMeToken(tokenValue, series, lastUsed); 26 | } 27 | 28 | @Transactional 29 | @Override 30 | public PersistentRememberMeToken getTokenForSeries(String seriesId) { 31 | RememberMeToken rememberMeToken = userService.findRememberMeToken(seriesId); 32 | if (rememberMeToken == null) { 33 | return null; 34 | } 35 | return new PersistentRememberMeToken(rememberMeToken.getEmail(), seriesId, rememberMeToken.getToken(), rememberMeToken.getLastUsedTime()); 36 | } 37 | 38 | @Transactional 39 | @Override 40 | public void removeUserTokens(String userId) { 41 | userService.removeRememberMeToken(userId); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/user/SocialUserDetailsHelper.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.user; 2 | 3 | import com.expercise.enums.SocialSignInProvider; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.social.connect.ConnectionData; 6 | import org.springframework.social.connect.UserProfile; 7 | 8 | public final class SocialUserDetailsHelper { 9 | 10 | private SocialUserDetailsHelper() { 11 | } 12 | 13 | public static String getImageUrl(ConnectionData connectionData) { 14 | String imageUrl = connectionData.getImageUrl(); 15 | if (SocialSignInProvider.Twitter.getProviderId().equals(connectionData.getProviderId())) { 16 | imageUrl = imageUrl.replace("_normal", "").replace("http://", "https://"); 17 | } 18 | return imageUrl; 19 | } 20 | 21 | public static String getFirstName(UserProfile userProfile) { 22 | return StringUtils.isNotBlank(userProfile.getFirstName()) ? userProfile.getFirstName() : userProfile.getName(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/user/SocialUserDetailsProvider.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.user; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 5 | import org.springframework.social.security.SocialUserDetailsService; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class SocialUserDetailsProvider implements SocialUserDetailsService { 10 | 11 | @Autowired 12 | private UserDetailsProvider userDetailsProvider; 13 | 14 | @Override 15 | public org.springframework.social.security.SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException { 16 | return (org.springframework.social.security.SocialUserDetails) userDetailsProvider.loadUserByUserId(userId); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/util/Prioritized.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.util; 2 | 3 | public interface Prioritized { 4 | 5 | int getPriority(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/util/PrioritySorter.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.util; 2 | 3 | import java.util.Comparator; 4 | 5 | public class PrioritySorter implements Comparator { 6 | 7 | @Override 8 | public int compare(Prioritized o1, Prioritized o2) { 9 | return Integer.compare(o1.getPriority(), o2.getPriority()); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/util/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.util; 2 | 3 | import com.expercise.domain.token.Token; 4 | import com.expercise.domain.token.TokenType; 5 | import com.expercise.domain.user.User; 6 | import com.expercise.repository.user.TokenRepository; 7 | import com.expercise.utils.TextUtils; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | public class TokenService { 13 | 14 | public static final int TOKEN_LENGTH = 32; 15 | 16 | @Autowired 17 | private TokenRepository tokenRepository; 18 | 19 | public String createTokenFor(User user, TokenType tokenType) { 20 | deletedOldTokenOf(user); 21 | Token token = new Token(); 22 | token.setUser(user); 23 | token.setTokenType(tokenType); 24 | token.setToken(generateUniqueTokenFor(tokenType)); 25 | tokenRepository.save(token); 26 | return token.getToken(); 27 | } 28 | 29 | private void deletedOldTokenOf(User user) { 30 | tokenRepository.deleteByUser(user); 31 | } 32 | 33 | private String generateUniqueTokenFor(TokenType tokenType) { 34 | String generatedToken; 35 | Token foundToken; 36 | do { 37 | generatedToken = TextUtils.randomAlphabetic(TOKEN_LENGTH); 38 | foundToken = findBy(generatedToken, tokenType); 39 | } 40 | while (foundToken != null); 41 | return generatedToken; 42 | } 43 | 44 | public Token findBy(String token, TokenType tokenType) { 45 | return tokenRepository.findByTokenAndTokenType(token, tokenType); 46 | } 47 | 48 | public void deleteToken(String token, TokenType tokenType) { 49 | Token foundToken = findBy(token, tokenType); 50 | if (foundToken != null) { 51 | tokenRepository.delete(foundToken); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/service/util/UrlService.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.util; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Service; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | 9 | @Service 10 | public class UrlService { 11 | 12 | @Value("${coding-expercise.root-url}") 13 | private String rootUrl; 14 | 15 | public String createUrlFor(String path) { 16 | return getRootUrl() + path; 17 | } 18 | 19 | public String getCanonical(HttpServletRequest request) { 20 | String uri = request.getRequestURI().replaceFirst("/expercise", ""); 21 | return getRootUrl() + uri; 22 | } 23 | 24 | private String getRootUrl() { 25 | return rootUrl.replaceFirst("://www.", "://"); 26 | } 27 | 28 | public String updateChallenge(Challenge challenge) { 29 | return "/challenges/updateChallenge/" + challenge.getId(); 30 | } 31 | 32 | public String challengeUrl(Challenge challenge) { 33 | return createUrlFor("/challenges/" + challenge.getId()); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/Clock.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | 6 | public final class Clock { 7 | 8 | private static Date frozenTime; 9 | 10 | private Clock() { 11 | } 12 | 13 | public static void freeze(Date frozenTime) { 14 | Clock.frozenTime = frozenTime; 15 | } 16 | 17 | public static void unfreeze() { 18 | frozenTime = null; 19 | } 20 | 21 | public static Date getTime() { 22 | return frozenTime != null ? frozenTime : Calendar.getInstance().getTime(); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | public final class Constants { 4 | 5 | public static final int MAX_CHALLENGE_TITLE_LENGTH = 64; 6 | 7 | public static final int MAX_TESTCASE_VALUE_LENGTH = 1024; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/CookieUtils.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.servlet.http.Cookie; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.Optional; 12 | 13 | @Component 14 | public class CookieUtils { 15 | 16 | @Autowired 17 | private HttpServletRequest request; 18 | 19 | public Optional getCookieValue(String name) { 20 | return Optional.ofNullable(request.getCookies()) 21 | .map(Arrays::asList) 22 | .orElse(new ArrayList<>()).stream() 23 | .filter(c -> c.getName().equals(name)) 24 | .findFirst() 25 | .map(Cookie::getValue); 26 | } 27 | 28 | public void setCookieValue(HttpServletResponse response, String name, String value, int maxAge) { 29 | Cookie cookie = new Cookie(name, value); 30 | cookie.setMaxAge(maxAge); 31 | cookie.setPath("/"); 32 | response.addCookie(cookie); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/EnvironmentUtils.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | public final class EnvironmentUtils { 4 | 5 | private EnvironmentUtils() { 6 | } 7 | 8 | public static boolean isProduction(String environment) { 9 | return "prod".equals(environment); 10 | } 11 | 12 | public static boolean isDevelopment(String environment) { 13 | return !isProduction(environment); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/NumberUtils.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.RoundingMode; 5 | 6 | public final class NumberUtils { 7 | 8 | public static final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100); 9 | 10 | public static final int DEFAULT_SCALE = 2; 11 | 12 | private NumberUtils() { 13 | } 14 | 15 | public static int toPercentage(int partial, int total) { 16 | return BigDecimal.valueOf(partial) 17 | .divide(BigDecimal.valueOf(total), DEFAULT_SCALE, RoundingMode.CEILING) 18 | .multiply(ONE_HUNDRED).intValue(); 19 | } 20 | 21 | public static Long parseLong(String text) { 22 | try { 23 | return Long.parseLong(text); 24 | } catch (Exception e) { 25 | return null; 26 | } 27 | } 28 | 29 | /** 30 | * Scales and translates a value (x) into a new range [a, b]. 31 | * Formula: f(x) = a + (b - a)(x - min)/(max - min) 32 | */ 33 | public static BigDecimal scaleAndTranslateIntoRange(BigDecimal value, BigDecimal actualMin, BigDecimal actualMax, BigDecimal desiredMin, BigDecimal desiredMax) { 34 | return desiredMax.subtract(desiredMin).multiply(value.subtract(actualMin)) 35 | .divide(actualMax.subtract(actualMin), RoundingMode.HALF_UP) 36 | .add(desiredMin) 37 | .setScale(2, RoundingMode.HALF_UP); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/PasswordEncoder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.authentication.encoding.ShaPasswordEncoder; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class PasswordEncoder { 9 | 10 | @Autowired 11 | private ShaPasswordEncoder shaPasswordEncoder; 12 | 13 | public String encode(String password) { 14 | return shaPasswordEncoder.encodePassword(password, null); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/TextUtils.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.commons.text.CharacterPredicates; 5 | import org.apache.commons.text.RandomStringGenerator; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | public final class TextUtils { 12 | 13 | private TextUtils() { 14 | } 15 | 16 | public static String randomAlphabetic(int length) { 17 | return new RandomStringGenerator.Builder() 18 | .withinRange('0', 'z') 19 | .filteredBy(CharacterPredicates.LETTERS) 20 | .build() 21 | .generate(length); 22 | } 23 | 24 | public static List splitSemicolonSeperated(String value) { 25 | String[] parts = StringUtils.split(StringUtils.defaultString(value), ";"); 26 | return new ArrayList<>(Arrays.asList(parts)); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/UrlUtils.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | import java.text.Normalizer; 4 | import java.util.Locale; 5 | import java.util.regex.Pattern; 6 | 7 | public final class UrlUtils { 8 | 9 | private UrlUtils() { 10 | } 11 | 12 | public static String makeBookmarkable(String text) { 13 | return Pattern.compile("\\p{InCombiningDiacriticalMarks}+") 14 | .matcher(Normalizer.normalize(text.replaceAll("[ı]", "i"), Normalizer.Form.NFD)) 15 | .replaceAll("") 16 | .toLowerCase(Locale.ENGLISH) 17 | .replaceAll("\\s+", " ") 18 | .trim() 19 | .replaceAll(" ", "-") 20 | .replaceAll("[^a-z0-9\\-]*", "") 21 | .replaceAll("(\\-)+", "-"); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/caching/Caching.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.caching; 2 | 3 | public interface Caching { 4 | 5 | void flush(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/caching/CachingSubject.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.caching; 2 | 3 | import com.expercise.service.challenge.SolutionCountService; 4 | import com.expercise.service.quote.QuoteService; 5 | 6 | public enum CachingSubject { 7 | 8 | SolutionCount(SolutionCountService.class), 9 | Quote(QuoteService.class); 10 | 11 | private Class cachingClass; 12 | 13 | CachingSubject(Class cachingClass) { 14 | this.cachingClass = cachingClass; 15 | } 16 | 17 | public Class getCachingClass() { 18 | return cachingClass; 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/collection/MapBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.collection; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class MapBuilder { 7 | 8 | private Map map = new HashMap<>(); 9 | 10 | public Map build() { 11 | return map; 12 | } 13 | 14 | public MapBuilder put(K key, V value) { 15 | map.put(key, value); 16 | return this; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/collection/RandomElement.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.collection; 2 | 3 | import org.apache.commons.lang3.RandomUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collection; 8 | import java.util.List; 9 | 10 | public final class RandomElement { 11 | 12 | private RandomElement() { 13 | } 14 | 15 | public static T from(Collection collection) { 16 | List list = new ArrayList<>(collection); 17 | return list.isEmpty() ? null : list.get(RandomUtils.nextInt(0, list.size())); 18 | } 19 | 20 | public static T from(T[] array) { 21 | return from(Arrays.asList(array)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/validation/BaseValidator.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.validation; 2 | 3 | import org.springframework.validation.BindingResult; 4 | import org.springframework.validation.FieldError; 5 | import org.springframework.validation.ObjectError; 6 | 7 | public abstract class BaseValidator { 8 | 9 | public abstract void validate(T object, BindingResult bindingResult); 10 | 11 | protected boolean validateField(boolean condition, BindingResult bindingResult, String objectName, String field, Object value, String[] codes) { 12 | if (!condition) { 13 | bindingResult.addError(new FieldError(objectName, field, value, false, codes, null, null)); 14 | return false; 15 | } else { 16 | return true; 17 | } 18 | } 19 | 20 | protected void addError(BindingResult bindingResult, String objectName, String[] codes) { 21 | bindingResult.addError(new ObjectError(objectName, codes, null, null)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/validation/EmailUniquenessValidator.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.validation; 2 | 3 | import com.expercise.service.user.UserService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.validation.ConstraintValidator; 8 | import javax.validation.ConstraintValidatorContext; 9 | 10 | @Component 11 | public class EmailUniquenessValidator implements ConstraintValidator { 12 | 13 | @Autowired 14 | private UserService userService; 15 | 16 | @Override 17 | public void initialize(UniqueEmail constraintAnnotation) { 18 | } 19 | 20 | @Override 21 | public boolean isValid(String value, ConstraintValidatorContext context) { 22 | return userService.emailNotRegisteredYet(value); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/validation/PasswordMatch.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.validation; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Constraint(validatedBy = PasswordMatchValidator.class) 10 | @Documented 11 | public @interface PasswordMatch { 12 | 13 | String message() default "{fieldMatch.error}"; 14 | 15 | Class[] groups() default {}; 16 | 17 | Class[] payload() default {}; 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/validation/PasswordMatchValidator.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.validation; 2 | 3 | import com.expercise.controller.user.model.PasswordModel; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.validation.ConstraintValidator; 8 | import javax.validation.ConstraintValidatorContext; 9 | 10 | @Component 11 | public class PasswordMatchValidator implements ConstraintValidator { 12 | 13 | @Override 14 | public void initialize(PasswordMatch constraintAnnotation) { 15 | } 16 | 17 | @Override 18 | public boolean isValid(PasswordModel passwordModel, ConstraintValidatorContext context) { 19 | String password = passwordModel.getPassword(); 20 | String passwordRetype = passwordModel.getPasswordRetype(); 21 | 22 | if (StringUtils.isBlank(password) || StringUtils.isBlank(passwordRetype)) { 23 | return false; 24 | } 25 | 26 | if (!password.equals(passwordRetype)) { 27 | context.buildConstraintViolationWithTemplate("passwordMatchError").addPropertyNode("passwordRetype").addConstraintViolation(); 28 | return false; 29 | } 30 | 31 | return true; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/validation/UniqueEmail.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.validation; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.Target; 7 | 8 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 9 | import static java.lang.annotation.ElementType.FIELD; 10 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 11 | 12 | @Target({FIELD, ANNOTATION_TYPE}) 13 | @Retention(RUNTIME) 14 | @Constraint(validatedBy = EmailUniquenessValidator.class) 15 | public @interface UniqueEmail { 16 | 17 | String message() default "{UniqueEmail.message}"; 18 | 19 | Class[] groups() default {}; 20 | 21 | Class[] payload() default {}; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/expercise/utils/validation/ValidationUtils.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils.validation; 2 | 3 | import org.springframework.context.support.DefaultMessageSourceResolvable; 4 | import org.springframework.validation.BindingResult; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | public class ValidationUtils { 10 | 11 | private ValidationUtils() { 12 | } 13 | 14 | public static List extractAllErrorCodes(BindingResult bindingResult) { 15 | return bindingResult.getAllErrors().stream() 16 | .map(DefaultMessageSourceResolvable::getCode) 17 | .collect(Collectors.toList()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | # Spring Boot 2 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect 3 | spring.jpa.hibernate.ddl-auto=validate 4 | spring.jpa.generate-ddl=false 5 | spring.datasource.url=jdbc:postgresql://localhost:5432/codingexpercise 6 | spring.datasource.driverClassName=org.postgresql.Driver 7 | spring.datasource.initialize=false 8 | spring.datasource.username=root 9 | spring.datasource.password=123qwe 10 | 11 | liquibase.enabled=true 12 | liquibase.contexts=dev 13 | 14 | # Coding Expercise 15 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | # Spring Boot 2 | spring.profiles.active=prod 3 | 4 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect 5 | spring.jpa.hibernate.ddl-auto=validate 6 | spring.jpa.generate-ddl=false 7 | spring.datasource.url=${SPRING_DATASOURCE_URL} 8 | spring.datasource.driverClassName=org.postgresql.Driver 9 | spring.datasource.initialize=false 10 | spring.datasource.username=${SPRING_DATASOURCE_USERNAME} 11 | spring.datasource.password=${SPRING_DATASOURCE_PASSWORD} 12 | 13 | spring.thymeleaf.cache=true 14 | 15 | liquibase.enabled=true 16 | liquibase.contexts=prod 17 | 18 | # Coding Expercise 19 | coding-expercise.root-url=https://coding.expercise.com 20 | coding-expercise.interpreter.api.url=${INTERPRETER_API_URL} 21 | 22 | coding-expercise.challenge-approval-strategy=admin 23 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Spring Boot 2 | spring.profiles.active=default 3 | 4 | spring.redis.port=6379 5 | 6 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 7 | spring.jpa.hibernate.ddl-auto=create-drop 8 | spring.jpa.generate-ddl=true 9 | spring.datasource.platform=H2 10 | spring.datasource.url=jdbc:h2:mem:codingexpercise:H2;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS PUBLIC 11 | spring.datasource.driverClassName=org.h2.Driver 12 | spring.datasource.initialize=true 13 | spring.datasource.username=sa 14 | spring.datasource.password= 15 | 16 | spring.thymeleaf.cache=false 17 | 18 | liquibase.enabled=false 19 | liquibase.change-log=classpath:db/changelog/changelog-master.xml 20 | 21 | # Coding Expercise 22 | coding-expercise.root-url=http://localhost:8080 23 | coding-expercise.interpreter.api.url=http://localhost:8081 24 | 25 | coding-expercise.email-status=deactive 26 | coding-expercise.send-grid.username= 27 | coding-expercise.send-grid.password= 28 | 29 | coding-expercise.twitter.consumer-key= 30 | coding-expercise.twitter.consumer-secret= 31 | 32 | coding-expercise.googleAnalytics.applicationKey= 33 | coding-expercise.slack.incomingWebhookUrl= 34 | 35 | coding-expercise.challenge-approval-strategy=auto 36 | coding-expercise.defaults.challengeId=-1 37 | -------------------------------------------------------------------------------- /src/main/resources/build.properties: -------------------------------------------------------------------------------- 1 | build.id=-1 -------------------------------------------------------------------------------- /src/main/resources/db/changelog/change-sets/change-set-1.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/change-sets/dev-change-set-1.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Test users :: all passwords are same : 123qwe 8 | 9 | INSERT INTO public.users (id, avatar, email, first_name, last_name, lingo, password, social_image_url, user_role) 10 | VALUES (nextval('SEQ_USERS'), null, 'ataturk@expercise.com', 'Mustafa Kemal', 'Ataturk', 'Turkish', 'fbfb386efea67e816f2dda0a8c94a98eb203757aebb3f55f183755a192d44467', null, 'Admin'); 11 | 12 | INSERT INTO public.users (id, avatar, email, first_name, last_name, lingo, password, social_image_url, user_role) 13 | VALUES (nextval('SEQ_USERS'), null, 'batu@expercise.com', 'Batuhan', 'Bayrakci', 'Turkish', 'fbfb386efea67e816f2dda0a8c94a98eb203757aebb3f55f183755a192d44467', null, 'Admin'); 14 | 15 | INSERT INTO public.users (id, avatar, email, first_name, last_name, lingo, password, social_image_url, user_role) 16 | VALUES (nextval('SEQ_USERS'), null, 'ufuk@expercise.com', 'Ufuk', 'Uzun', 'Turkish', 'fbfb386efea67e816f2dda0a8c94a98eb203757aebb3f55f183755a192d44467', null, 'Admin'); 17 | 18 | INSERT INTO public.users (id, avatar, email, first_name, last_name, lingo, password, social_image_url, user_role) 19 | VALUES (nextval('SEQ_USERS'), null, 'dennis@expercise.com', 'Dennis', 'Ritchie', 'English', 'fbfb386efea67e816f2dda0a8c94a98eb203757aebb3f55f183755a192d44467', null, 'User'); 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/resources/db/changelog/changelog-master.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/emails/emailLayout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | Email Content 6 |
7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/emails/forgotMyPasswordEmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |

7 |

8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/messagesForEmails_en.properties: -------------------------------------------------------------------------------- 1 | email.hello = Hello {0}, 2 | 3 | forgotMyPassword.subject = Password Reset 4 | forgotMyPassword.info = Please click link below to reset your password. 5 | forgotMyPassword.urlText = Reset Password -------------------------------------------------------------------------------- /src/main/resources/messagesForEmails_pt.properties: -------------------------------------------------------------------------------- 1 | email.hello = Hello {0}, 2 | 3 | forgotMyPassword.subject = Password Reset 4 | forgotMyPassword.info = Please click link below to reset your password. 5 | forgotMyPassword.urlText = Reset Password -------------------------------------------------------------------------------- /src/main/resources/messagesForEmails_tr.properties: -------------------------------------------------------------------------------- 1 | email.hello = Merhaba {0}, 2 | 3 | forgotMyPassword.subject = Parola Sıfırlama 4 | forgotMyPassword.info = Parolanızı sıfırlamak için aşağıdaki bağlantıya tıklayınız. 5 | forgotMyPassword.urlText = Parolamı sıfırla -------------------------------------------------------------------------------- /src/main/resources/public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/main/resources/public/fonts/black-rose.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/black-rose.ttf -------------------------------------------------------------------------------- /src/main/resources/public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/resources/public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/resources/public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/resources/public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/public/img/ATTRIBUTION.md: -------------------------------------------------------------------------------- 1 | Attribution 2 | =========== 3 | 4 | The original version of Avatars (sprites/avatars-sprite.svg) designed by Freepik.com and named as [Animal avatars collection](http://www.freepik.com/free-vector/animal-avatars-collection_766290.htm). 5 | 6 | The original version of background/green-landscape.svg designed by Freepik.com and named as [Urban landscape with a big wheel](http://www.freepik.com/free-vector/urban-landscape-with-a-big-wheel_766759.htm). 7 | 8 | The original version of background/programming-on-the-cloud.svg designed by Freepik.com and named as [Businessman using a tablet](http://www.freepik.com/free-vector/businessman-using-a-tablet_767018.htm). 9 | 10 | The original version of Assistant Robot (assistant-robot.svg) designed by Freepik.com and named as [Retro robots vector elements](http://www.freepik.com/free-vector/retro-robots-vector-elements_721189.htm). -------------------------------------------------------------------------------- /src/main/resources/public/img/expercise-logo-rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/img/expercise-logo-rounded.png -------------------------------------------------------------------------------- /src/main/resources/public/img/expercise-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/img/expercise-logo.png -------------------------------------------------------------------------------- /src/main/resources/public/img/flags/tr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/public/img/java8-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/img/java8-logo.png -------------------------------------------------------------------------------- /src/main/resources/public/img/javascript-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/img/javascript-logo.png -------------------------------------------------------------------------------- /src/main/resources/public/img/python-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/img/python-logo.png -------------------------------------------------------------------------------- /src/main/resources/public/img/sprite-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/img/sprite-green.png -------------------------------------------------------------------------------- /src/main/resources/public/img/sprite-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expercise/coding-expercise/02ee5b54898a9848019c0aa716c424328c97e918/src/main/resources/public/img/sprite-orange.png -------------------------------------------------------------------------------- /src/main/resources/public/js/bootstrapper.js: -------------------------------------------------------------------------------- 1 | var coreModules = [ 2 | expercise, 3 | expercise.utils, 4 | expercise.Header, 5 | expercise.Locale 6 | ]; 7 | 8 | $(function () { 9 | initCoreModules(); 10 | initPageSpecificModules(); 11 | }); 12 | 13 | function initCoreModules() { 14 | coreModules.forEach(function (eachModule) { 15 | initModule(eachModule); 16 | }); 17 | } 18 | 19 | function initPageSpecificModules() { 20 | var $moduleNamesHolder = $('#javaScriptModules'); 21 | if ($moduleNamesHolder.length) { 22 | var pageSpecificModules = $moduleNamesHolder.val().split(" "); 23 | pageSpecificModules.forEach(function (eachModule) { 24 | initModule(expercise[eachModule]); 25 | }); 26 | } 27 | } 28 | 29 | function initModule(module) { 30 | module.constructor && module.constructor(); 31 | module.bindEvents && module.bindEvents(); 32 | } -------------------------------------------------------------------------------- /src/main/resources/public/js/codemirror/anyword-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function (mod) { 5 | mod(CodeMirror); 6 | })(function (CodeMirror) { 7 | "use strict"; 8 | 9 | var WORD = /[\w$]+/, RANGE = 500; 10 | 11 | CodeMirror.registerHelper("hint", "anyword", function (editor, options) { 12 | var word = options && options.word || WORD; 13 | var range = options && options.range || RANGE; 14 | var cur = editor.getCursor(), curLine = editor.getLine(cur.line); 15 | var end = cur.ch, start = end; 16 | while (start && word.test(curLine.charAt(start - 1))) --start; 17 | var curWord = start != end && curLine.slice(start, end); 18 | 19 | var list = options && options.list || [], seen = {}; 20 | var re = new RegExp(word.source, "g"); 21 | for (var dir = -1; dir <= 1; dir += 2) { 22 | var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir; 23 | for (; line != endLine; line += dir) { 24 | var text = editor.getLine(line), m; 25 | while (m = re.exec(text)) { 26 | if (line == cur.line && m[0] === curWord) continue; 27 | if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) { 28 | seen[m[0]] = true; 29 | list.push(m[0]); 30 | } 31 | } 32 | } 33 | } 34 | return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)}; 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/main/resources/public/js/expercise.js: -------------------------------------------------------------------------------- 1 | expercise = { 2 | 3 | assistant: new Assistant(), 4 | 5 | constructor: function () { 6 | this.assistant.init({ 7 | image: expercise.utils.urlFor('img/assistant-robot.svg'), 8 | width: '100px', 9 | height: '166px' 10 | }); 11 | }, 12 | 13 | bindEvents: function () { 14 | this.configureTooltips(); 15 | }, 16 | 17 | configureTooltips: function () { 18 | var $tooltips = $('[data-toggle="tooltip"]'); 19 | $tooltips.tooltip(); 20 | 21 | var $showOnLoadTooltips = $tooltips.filter('.showOnLoad'); 22 | $showOnLoadTooltips.tooltip('show'); 23 | setTimeout(function () { 24 | $showOnLoadTooltips.tooltip('hide'); 25 | }, 2000); 26 | } 27 | 28 | }; -------------------------------------------------------------------------------- /src/main/resources/public/js/forgottenPassword.js: -------------------------------------------------------------------------------- 1 | expercise.ForgottenPassword = { 2 | 3 | bindEvents: function () { 4 | $('.sendForgotEmailBtn').click(this.sendResetEmail); 5 | }, 6 | 7 | sendResetEmail: function () { 8 | var emailVal = $('#email').val(); 9 | expercise.utils.post("forgotMyPassword/sendForgotMyPasswordEmail", {email: emailVal}, expercise.ForgottenPassword.responseCallback); 10 | }, 11 | 12 | responseCallback: function (response) { 13 | var resultContainer = $('.forgotMyPasswordResult'); 14 | resultContainer.removeClass('alert-success').removeClass('alert-danger'); 15 | resultContainer.addClass('alert'); 16 | if (response['success']) { 17 | resultContainer.addClass('alert-success'); 18 | $('#email').val(''); 19 | } else { 20 | resultContainer.addClass('alert-danger'); 21 | } 22 | var message = expercise.utils.i18n(response['messageKey']); 23 | resultContainer.find('.forgotMyPasswordResultMessage').text(message); 24 | } 25 | 26 | }; -------------------------------------------------------------------------------- /src/main/resources/public/js/header.js: -------------------------------------------------------------------------------- 1 | expercise.Header = { 2 | 3 | $header: $('#header'), 4 | 5 | bindEvents: function () { 6 | this.menuToggles(); 7 | }, 8 | 9 | menuToggles: function () { 10 | if (expercise.utils.isMobileClient()) { 11 | return; 12 | } 13 | 14 | $('#userMenuDropdown').mouseenter(function () { 15 | if ($('#userMenuDropdownContainer').hasClass('open') == false) { 16 | $(this).dropdown('toggle'); 17 | } 18 | }); 19 | 20 | $('#lingoMenuDropdown').mouseenter(function () { 21 | if ($('#lingoMenuDropdownContainer').hasClass('open') == false) { 22 | $(this).dropdown('toggle'); 23 | } 24 | }); 25 | } 26 | 27 | }; -------------------------------------------------------------------------------- /src/main/resources/public/js/home.js: -------------------------------------------------------------------------------- 1 | expercise.Home = { 2 | 3 | constructor: function () { 4 | this.showAssistant(); 5 | }, 6 | 7 | showAssistant: function () { 8 | expercise.assistant.speak( 9 | { 10 | title: expercise.utils.i18n('assistant.home.welcome.title'), 11 | message: expercise.utils.i18n('assistant.home.welcome.content', [expercise.utils.urlFor('start-coding')]), 12 | buttonText: expercise.utils.i18n('assistant.home.welcome.button'), 13 | onButtonClick: function () { 14 | expercise.utils.go(expercise.utils.urlFor('start-coding')); 15 | } 16 | } 17 | ); 18 | expercise.assistant.hide(15); 19 | } 20 | 21 | }; -------------------------------------------------------------------------------- /src/main/resources/public/js/locale.js: -------------------------------------------------------------------------------- 1 | expercise.Locale = { 2 | 3 | bindEvents: function () { 4 | $('#lingoSelection').find('a').click(function () { 5 | expercise.CodeEditor.resetMode(); 6 | }); 7 | } 8 | 9 | }; -------------------------------------------------------------------------------- /src/main/resources/public/js/profile.js: -------------------------------------------------------------------------------- 1 | expercise.Profile = { 2 | 3 | bindEvents: function () { 4 | $('#avatarsCollapse a').click(this.selectAvatar); 5 | 6 | $('#connectTwitterAccount').click(function (e) { 7 | e.preventDefault(); 8 | 9 | $(e.target).parents('form').submit(); 10 | }); 11 | }, 12 | 13 | selectAvatar: function (event) { 14 | var avatarType = $(event.target).closest('a').data('avatar-type'); 15 | var url = expercise.utils.urlFor("user/selectAvatar/" + avatarType); 16 | expercise.utils.go(url); 17 | } 18 | 19 | }; -------------------------------------------------------------------------------- /src/main/resources/public/js/tagListing.js: -------------------------------------------------------------------------------- 1 | expercise.TagListing = { 2 | 3 | constructor: function () { 4 | var that = this; 5 | 6 | if ($('#newMember').val() == "true") { 7 | setTimeout(function () { 8 | bootbox.dialog({ 9 | title: expercise.utils.i18n('login.register.welcome'), 10 | message: expercise.utils.i18n('login.register.welcome.messageForTagListingPage'), 11 | closeButton: false, 12 | buttons: { 13 | ok: { 14 | label: expercise.utils.i18n('button.continue'), 15 | callback: function () { 16 | setTimeout(function () { 17 | that.showAssistant(); 18 | }, 1000); 19 | } 20 | } 21 | } 22 | }); 23 | }, 500); 24 | } else { 25 | this.showAssistant(); 26 | } 27 | }, 28 | 29 | bindEvents: function () { 30 | }, 31 | 32 | showAssistant: function () { 33 | expercise.assistant.speak( 34 | { 35 | title: expercise.utils.i18n('assistant.tags.whatIsTags.title'), 36 | message: expercise.utils.i18n('assistant.tags.whatIsTags.content'), 37 | buttonText: expercise.utils.i18n('assistant.tags.whatIsTags.button'), 38 | onButtonClick: function () { 39 | expercise.assistant.hide(); 40 | } 41 | } 42 | ); 43 | expercise.assistant.hide(30); 44 | } 45 | 46 | }; -------------------------------------------------------------------------------- /src/main/resources/public/js/utils.js: -------------------------------------------------------------------------------- 1 | expercise.utils = { 2 | 3 | urlFor: function (uri) { 4 | return $('#contextPath').val() + uri; 5 | }, 6 | 7 | go: function (url) { 8 | window.location = url; 9 | }, 10 | 11 | i18n: function (key, args) { 12 | var value = window['messages'][key]; 13 | 14 | (args || []).forEach(function (each) { 15 | value = value.replace(/{[0-9]}/g, each); 16 | }); 17 | 18 | return value; 19 | }, 20 | 21 | post: function (url, data, success) { 22 | $.ajax({ 23 | type: 'POST', dataType: 'json', contentType: 'application/json; charset=utf-8', 24 | url: expercise.utils.urlFor(url), 25 | data: JSON.stringify(data), 26 | success: success 27 | }); 28 | }, 29 | 30 | resetHash: function () { 31 | location.hash = ''; 32 | }, 33 | 34 | setLoadingState: function (config) { 35 | var $icon = config.element.find('i'); 36 | config.oldIcon = $icon.attr('class'); 37 | $icon.addClass('fa fa-hourglass-half'); 38 | config.element.addClass('disabled'); 39 | return config; 40 | }, 41 | 42 | resetLoadingState: function (config) { 43 | config.element.removeClass('disabled'); 44 | var $icon = config.element.find('i'); 45 | $icon.removeClass('fa fa-hourglass-half'); 46 | $icon.addClass(config.oldIcon); 47 | }, 48 | 49 | isMobileClient: function () { 50 | return $('#mobileClient').val() == "true"; 51 | }, 52 | 53 | scrollTop: function () { 54 | $(window).scrollTop(0); 55 | } 56 | 57 | }; -------------------------------------------------------------------------------- /src/main/resources/public/style/addUpdateChallenge.less: -------------------------------------------------------------------------------- 1 | #testCasesTable > tbody tr, #testCasesTable > tbody tr:hover { 2 | cursor: pointer; 3 | } 4 | 5 | #testCasesTable > tbody tr:hover { 6 | background-color: #eee; 7 | } 8 | 9 | #testCasesTable > tbody > tr > td { 10 | vertical-align: middle; 11 | } 12 | 13 | .removeInput, #addNewInput, .removeTestCase, #addNewTestCase { 14 | cursor: pointer; 15 | } 16 | 17 | #testCasesTable > tbody > tr > td > input { 18 | margin-bottom: 5px; 19 | } 20 | 21 | #testCasesTable > tbody > tr > td > a.btn { 22 | margin-bottom: 5px; 23 | } -------------------------------------------------------------------------------- /src/main/resources/public/style/avatars.less: -------------------------------------------------------------------------------- 1 | .avatar { 2 | @avatar-count: 8; 3 | @default-width: 32px; 4 | @background-size: 256px; 5 | 6 | display: inline-block; 7 | margin-bottom: -4px; 8 | background: url(../img/sprites/avatars-sprite.svg) no-repeat; 9 | width: @default-width; 10 | height: @default-width; 11 | 12 | .avatars-for-size(1); 13 | 14 | &.small { 15 | .avatars-for-size(0.5); 16 | } 17 | 18 | &.medium { 19 | .avatars-for-size(1.5); 20 | } 21 | 22 | &.large { 23 | .avatars-for-size(4.5); 24 | } 25 | 26 | .avatars-for-size(@multiplier) { 27 | width: @default-width * @multiplier; 28 | height: @default-width * @multiplier; 29 | background-size: @background-size * @multiplier; 30 | 31 | .background-position(@avatar-index) { 32 | background-position: (@multiplier * (@avatar-index - 1) * (-1 * @default-width)) 0; 33 | } 34 | 35 | .generate-avatars(@avatar-count); 36 | 37 | .generate-avatars(@n, @i: 1) when (@i =< @n) { 38 | &.animal-avatar-@{i} { 39 | .background-position(@i); 40 | } 41 | .generate-avatars(@n, (@i + 1)); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/resources/public/style/bootstrap-tagsinput.css: -------------------------------------------------------------------------------- 1 | .bootstrap-tagsinput { 2 | background-color: #fff; 3 | border: 1px solid #ccc; 4 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 5 | display: inline-block; 6 | padding: 4px 6px; 7 | color: #555; 8 | vertical-align: middle; 9 | border-radius: 4px; 10 | max-width: 100%; 11 | line-height: 22px; 12 | cursor: text; 13 | } 14 | .bootstrap-tagsinput input { 15 | border: none; 16 | box-shadow: none; 17 | outline: none; 18 | background-color: transparent; 19 | padding: 0 6px; 20 | margin: 0; 21 | width: auto; 22 | max-width: inherit; 23 | } 24 | .bootstrap-tagsinput.form-control input::-moz-placeholder { 25 | color: #777; 26 | opacity: 1; 27 | } 28 | .bootstrap-tagsinput.form-control input:-ms-input-placeholder { 29 | color: #777; 30 | } 31 | .bootstrap-tagsinput.form-control input::-webkit-input-placeholder { 32 | color: #777; 33 | } 34 | .bootstrap-tagsinput input:focus { 35 | border: none; 36 | box-shadow: none; 37 | } 38 | .bootstrap-tagsinput .tag { 39 | margin-right: 2px; 40 | color: white; 41 | } 42 | .bootstrap-tagsinput .tag [data-role="remove"] { 43 | margin-left: 8px; 44 | cursor: pointer; 45 | } 46 | .bootstrap-tagsinput .tag [data-role="remove"]:after { 47 | content: "x"; 48 | padding: 0px 2px; 49 | } 50 | .bootstrap-tagsinput .tag [data-role="remove"]:hover { 51 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 52 | } 53 | .bootstrap-tagsinput .tag [data-role="remove"]:hover:active { 54 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 55 | } 56 | -------------------------------------------------------------------------------- /src/main/resources/public/style/challenge.less: -------------------------------------------------------------------------------- 1 | textarea.codeEditor { 2 | width: 100%; 3 | height: 100%; 4 | min-height: 225px; 5 | resize: none; 6 | } 7 | 8 | textarea.description { 9 | resize: none; 10 | } 11 | 12 | .editorOptions { 13 | border: 1px solid #ccc; 14 | padding: 5px; 15 | border-bottom: 0; 16 | 17 | .glyphicon { 18 | margin-right: 3px; 19 | } 20 | 21 | .editorButton { 22 | margin-left: 5px; 23 | cursor: pointer; 24 | line-height: 28px !important; 25 | } 26 | 27 | .keyMapSelectionPanel { 28 | margin-left: 5px; 29 | 30 | &.checkbox { 31 | display: inline; 32 | } 33 | 34 | input[type=checkbox] { 35 | margin-top: 8px; 36 | } 37 | } 38 | 39 | #languageSelection { 40 | width: 25%; 41 | display: inline; 42 | } 43 | } 44 | 45 | div.challengeInfo { 46 | padding-bottom: 10px; 47 | } 48 | 49 | table#userTestCaseStatus { 50 | .resultMessage { 51 | color: #444; 52 | padding: 5px; 53 | } 54 | 55 | tbody td.hiddenRow { 56 | padding: 0; 57 | } 58 | 59 | pre { 60 | border: 0; 61 | background: none; 62 | } 63 | } 64 | 65 | table { 66 | tr td > div.testCaseValue { 67 | } 68 | 69 | tr.testCaseRow { 70 | cursor: pointer; 71 | 72 | &.success { 73 | color: green; 74 | 75 | .testCaseStatus { 76 | &:before { 77 | content: "\e084"; 78 | } 79 | } 80 | } 81 | 82 | &.danger { 83 | color: #a94442; 84 | 85 | .testCaseStatus { 86 | &:before { 87 | content: "\e083"; 88 | } 89 | } 90 | } 91 | 92 | &.info { 93 | color: #666666; 94 | cursor: default; 95 | 96 | .testCaseStatus { 97 | &:before { 98 | content: "\e085"; 99 | } 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /src/main/resources/public/style/expercise.less: -------------------------------------------------------------------------------- 1 | @import "common.less"; 2 | @import "avatars.less"; 3 | @import "challenge.less"; 4 | @import "profile.less"; 5 | @import "index.less"; 6 | @import "header.less"; 7 | @import "footer.less"; 8 | @import "tagListing.less"; 9 | @import "addUpdateChallenge.less"; 10 | @import "login.less"; -------------------------------------------------------------------------------- /src/main/resources/public/style/footer.less: -------------------------------------------------------------------------------- 1 | div#footer { 2 | margin-top: 20px; 3 | margin-bottom: 20px; 4 | text-align: center; 5 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); 6 | 7 | .text-muted { 8 | color: whitesmoke; 9 | } 10 | 11 | a { 12 | color: whitesmoke; 13 | text-decoration: none; 14 | font-size: 15px; 15 | font-weight: bold; 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/resources/public/style/index.less: -------------------------------------------------------------------------------- 1 | .mainPage.jumbotron { 2 | background-color: #ffffff; 3 | padding-bottom: 10px; 4 | padding-top: 30px; 5 | text-align: center; 6 | 7 | h1 { 8 | font-size: 70px; 9 | font-family: BlackRose; 10 | margin-top: 0; 11 | } 12 | 13 | h2 { 14 | font-size: 20px; 15 | margin-top: 10px; 16 | } 17 | 18 | h3 { 19 | font-size: 20px; 20 | } 21 | 22 | h4 { 23 | margin-top: 15px; 24 | margin-bottom: 5px; 25 | } 26 | 27 | .row { 28 | text-align: left; 29 | } 30 | 31 | p { 32 | font-size: 15px; 33 | } 34 | 35 | .annotationText { 36 | font-size: 10px; 37 | font-style: oblique; 38 | padding-top: 10px; 39 | } 40 | 41 | .quote { 42 | font-style: oblique; 43 | 44 | &:after { 45 | content: close-quote; 46 | } 47 | 48 | &:before { 49 | content: open-quote; 50 | } 51 | } 52 | 53 | #supportedLanguages { 54 | a.noTextDecoration:hover { 55 | text-decoration: none; 56 | } 57 | 58 | img.logo { 59 | height: 110px; 60 | width: 110px; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/resources/public/style/login.less: -------------------------------------------------------------------------------- 1 | .rememberMeContainer { 2 | &.col-xs-2 { 3 | padding-left: 0; 4 | padding-right: 0; 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/public/style/profile.less: -------------------------------------------------------------------------------- 1 | .userPicture { 2 | text-align: center; 3 | 4 | .glyphicon, .avatar { 5 | font-size: 150px; 6 | color: #999; 7 | } 8 | 9 | #avatarsCollapse .avatar { 10 | font-size: 50px; 11 | margin-top: 3px; 12 | } 13 | 14 | .avatarSelectionContainer { 15 | margin-bottom: 10px; 16 | 17 | .socialImage { 18 | margin-top: -32px; 19 | } 20 | } 21 | } 22 | 23 | .userInfos label { 24 | text-align: right; 25 | } 26 | 27 | .userProgress { 28 | text-align: center; 29 | margin-top: 10px; 30 | } 31 | 32 | .userProgress .experiencePoint span:first-child { 33 | font-size: 85%; 34 | } 35 | 36 | .userProgress .experiencePoint div:first-child { 37 | margin-bottom: 6px; 38 | } 39 | 40 | .userProgress .experiencePoint .label { 41 | border-radius: 17px; 42 | font-size: 22px; 43 | } 44 | 45 | .experiencePoint { 46 | padding-top: 0px; 47 | } 48 | 49 | .profileContainer:nth-child(n+2) { 50 | margin-top: 20px; 51 | } -------------------------------------------------------------------------------- /src/main/resources/public/style/show-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-hints { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | list-style: none; 6 | 7 | margin: 0; 8 | padding: 2px; 9 | 10 | -webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, .2); 11 | -moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, .2); 12 | box-shadow: 2px 3px 5px rgba(0, 0, 0, .2); 13 | border-radius: 3px; 14 | border: 1px solid silver; 15 | 16 | background: white; 17 | font-size: 90%; 18 | font-family: monospace; 19 | 20 | max-height: 20em; 21 | overflow-y: auto; 22 | } 23 | 24 | .CodeMirror-hint { 25 | margin: 0; 26 | padding: 0 4px; 27 | border-radius: 2px; 28 | max-width: 19em; 29 | overflow: hidden; 30 | white-space: pre; 31 | color: black; 32 | cursor: pointer; 33 | } 34 | 35 | li.CodeMirror-hint-active { 36 | background: #08f; 37 | color: white; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/public/style/tagListing.less: -------------------------------------------------------------------------------- 1 | .tagList { 2 | text-align: center; 3 | margin-bottom: 15px; 4 | margin-top: 30px; 5 | 6 | a.tagLink { 7 | margin-right: 12px; 8 | font-weight: bold; 9 | font-size: 12px; 10 | line-height: 36px; 11 | 12 | &.x2 { 13 | font-size: 18px; 14 | } 15 | 16 | &.x3 { 17 | font-size: 24px; 18 | } 19 | 20 | &.x4 { 21 | font-size: 30px; 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/resources/templates/error/errorPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

Ups!

10 | 11 |

Page not found!

12 | 13 |
14 | Sorry, requested page not found! 15 |
16 | 17 |
18 | 19 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/inputHiddens.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/templates/tag/tagsList.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |

12 |
13 |
14 | 15 |
16 |
17 | 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/templates/user/forgotMyPassword.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |

12 | 13 | 14 |

15 | 16 |
17 |

18 |
19 | 20 |
21 |
22 | 23 | 24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/test/groovy/com/expercise/controller/RedirectUtilsSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.expercise.controller 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class RedirectUtilsSpec extends Specification { 7 | 8 | @Unroll 9 | def "should redirect #path to #redirectedPath"() { 10 | expect: 11 | RedirectUtils.redirectTo(path).getViewName() == redirectedPath 12 | 13 | where: 14 | path | redirectedPath 15 | "/" | "redirect:/" 16 | "/signin" | "redirect:/signin" 17 | "/signin?newmember" | "redirect:/signin?newmember" 18 | "/404" | "redirect:/404" 19 | "/403" | "redirect:/403" 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/groovy/com/expercise/service/email/EmailTemplateProcessorSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.expercise.service.email 2 | 3 | import com.expercise.service.i18n.MessageService 4 | import org.thymeleaf.context.Context 5 | import spock.lang.Specification 6 | 7 | class EmailTemplateProcessorSpec extends Specification { 8 | 9 | EmailTemplateProcessor processor 10 | 11 | TemplateEngineWrapper templateEngineWrapper = Mock() 12 | MessageService messageService = Mock() 13 | 14 | Context contextArgumentCaptor 15 | 16 | def setup() { 17 | processor = new EmailTemplateProcessor(templateEngineWrapper: templateEngineWrapper, messageService: messageService) 18 | } 19 | 20 | def "should create multilingual email from proper parameters"() { 21 | when: 22 | String emailContent = processor.createEmail("contentFileName", ["paramKey": "paramValue"]) 23 | 24 | then: 25 | emailContent == "email content with paramValue" 26 | 27 | and: "send right parameters to the template engine" 28 | 1 * templateEngineWrapper.process("contentFileName", { 29 | contextArgumentCaptor = it 30 | } as Context) >> "email content with paramValue" 31 | contextArgumentCaptor.getVariables().get("paramKey") == "paramValue" 32 | contextArgumentCaptor.getVariables().get("msg") == messageService 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/groovy/com/expercise/service/email/SendGridEmailServiceSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.expercise.service.email 2 | 3 | import com.expercise.service.email.model.Email 4 | import com.sendgrid.SendGrid 5 | import spock.lang.Specification 6 | 7 | class SendGridEmailServiceSpec extends Specification { 8 | 9 | EmailSenderService service 10 | 11 | SendGrid sendGridClient = Mock() 12 | 13 | SendGrid.Email emailArgument 14 | 15 | def setup() { 16 | service = new SendGridEmailSenderService(sendGridClient: sendGridClient) 17 | } 18 | 19 | def "should send email via SendGrid with proper parameters"() { 20 | given: 21 | Email emailToSend = new Email(to: "user@expercise.com", from: "noreply@expercise.com", subject: "Subject of the email", content: "Content of the email") 22 | 23 | when: 24 | service.send(emailToSend) 25 | 26 | then: 27 | 1 * sendGridClient.send({ emailArgument = it }) >> new SendGrid.Response(200, "email sent successfully") 28 | emailArgument.getTos()[0] == "user@expercise.com" 29 | emailArgument.getFrom() == "noreply@expercise.com" 30 | emailArgument.getSubject() == "Subject of the email" 31 | emailArgument.getHtml() == "Content of the email" 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/groovy/com/expercise/service/util/UrlServiceSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.expercise.service.util 2 | 3 | import org.springframework.mock.web.MockHttpServletRequest 4 | import spock.lang.Specification 5 | 6 | class UrlServiceSpec extends Specification { 7 | 8 | UrlService service 9 | 10 | MockHttpServletRequest mockHttpServletRequest 11 | 12 | def setup() { 13 | service = new UrlService(rootUrl: "http://coding.expercise.com") 14 | mockHttpServletRequest = new MockHttpServletRequest() 15 | } 16 | 17 | def "should create url for specified path"() { 18 | expect: 19 | "http://coding.expercise.com/mypath" == service.createUrlFor("/mypath") 20 | } 21 | 22 | def "should create canonical url with request uri and root url, without www"() { 23 | given: 24 | mockHttpServletRequest.setRequestURI("/tags") 25 | 26 | expect: 27 | "http://coding.expercise.com/tags" == service.getCanonical(mockHttpServletRequest) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/groovy/com/expercise/utils/NumberUtilsSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.expercise.utils 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class NumberUtilsSpec extends Specification { 7 | 8 | @Unroll 9 | def "should calculate percentage as #percentage when partial is #partial and total is #total"(int partial, int total, int percentage) { 10 | expect: 11 | NumberUtils.toPercentage(partial, total) == percentage 12 | 13 | where: 14 | partial | total | percentage 15 | 1 | 2 | 50 16 | 49 | 100 | 49 17 | 3 | 4 | 75 18 | 11 | 12 | 92 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/groovy/com/expercise/utils/UrlUtilsSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.expercise.utils 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class UrlUtilsSpec extends Specification { 7 | 8 | @Unroll 9 | def "should make '#notBookmarkableText' bookmarkable '#bookmarkableText'"() { 10 | expect: 11 | UrlUtils.makeBookmarkable(notBookmarkableText) == bookmarkableText; 12 | 13 | where: 14 | notBookmarkableText | bookmarkableText 15 | "Not Bookmarkable" | "not-bookmarkable" 16 | " Trim Spaces " | "trim-spaces" 17 | "Add only one hyphen for middle spaces" | "add-only-one-hyphen-for-middle-spaces" 18 | "Remove & special \" * -- ' chars" | "remove-special-chars" 19 | "Türkçe Not Bookmarkable Bir İsim İıŞşÇçÖöÜüĞğ" | "turkce-not-bookmarkable-bir-isim-iissccoouugg" 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/BaseSpringIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.test.context.ActiveProfiles; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import javax.persistence.EntityManager; 10 | import javax.persistence.PersistenceContext; 11 | 12 | @SpringBootTest 13 | @RunWith(SpringRunner.class) 14 | @ActiveProfiles(profiles = "test") 15 | @Transactional 16 | public abstract class BaseSpringIntegrationTest { 17 | 18 | @PersistenceContext 19 | private EntityManager entityManager; 20 | 21 | protected final EntityManager getEntityManager() { 22 | return entityManager; 23 | } 24 | 25 | protected void flushAndClear() { 26 | getEntityManager().flush(); 27 | getEntityManager().clear(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/controller/HomeControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller; 2 | 3 | import com.expercise.domain.quote.Quote; 4 | import com.expercise.service.quote.QuoteService; 5 | import com.expercise.testutils.builder.QuoteBuilder; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import static org.hamcrest.Matchers.equalTo; 14 | import static org.junit.Assert.assertThat; 15 | import static org.mockito.Mockito.when; 16 | 17 | @RunWith(MockitoJUnitRunner.class) 18 | public class HomeControllerTest { 19 | 20 | @InjectMocks 21 | private HomeController controller; 22 | 23 | @Mock 24 | private QuoteService quoteService; 25 | 26 | @Test 27 | public void shouldRenderIndexView() { 28 | ModelAndView modelAndView = controller.homePage(); 29 | 30 | assertThat(modelAndView.getViewName(), equalTo("index")); 31 | } 32 | 33 | @Test 34 | public void shouldShowQuoteOnHomePage() { 35 | Quote quote = new QuoteBuilder().buildWithRandomId(); 36 | 37 | when(quoteService.randomQuote()).thenReturn(quote); 38 | 39 | ModelAndView modelAndView = controller.homePage(); 40 | 41 | assertThat(modelAndView.getModel().get("quote"), equalTo(quote)); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/test/java/com/expercise/controller/user/management/UserListingControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.controller.user.management; 2 | 3 | import com.expercise.domain.user.User; 4 | import com.expercise.service.user.UserService; 5 | import com.expercise.testutils.builder.UserBuilder; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | import static org.hamcrest.CoreMatchers.equalTo; 17 | import static org.hamcrest.CoreMatchers.hasItems; 18 | import static org.hamcrest.MatcherAssert.assertThat; 19 | import static org.mockito.Mockito.when; 20 | 21 | @RunWith(MockitoJUnitRunner.class) 22 | public class UserListingControllerTest { 23 | 24 | @InjectMocks 25 | private UserListingController controller; 26 | 27 | @Mock 28 | private UserService userService; 29 | 30 | @Test 31 | public void shouldGetAllUsers() { 32 | User user0 = new UserBuilder().buildWithRandomId(); 33 | User user1 = new UserBuilder().buildWithRandomId(); 34 | 35 | when(userService.findAll()).thenReturn(Arrays.asList(user0, user1)); 36 | 37 | ModelAndView modelAndView = controller.listUsersForAdmin(); 38 | 39 | List users = (List) modelAndView.getModel().get("users"); 40 | 41 | assertThat(users.size(), equalTo(2)); 42 | assertThat(users, hasItems(user0, user1)); 43 | assertThat(modelAndView.getViewName(), equalTo("user/userList")); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/test/java/com/expercise/domain/user/UserTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.domain.user; 2 | 3 | import com.expercise.testutils.builder.UserBuilder; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.Matchers.equalTo; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class UserTest { 10 | 11 | @Test 12 | public void shouldReturnBookmarkableUrlByIdAndFullName() { 13 | User user = new UserBuilder().id(1989L).firstName("Ufuk").lastName("Uzun").build(); 14 | 15 | assertThat(user.getBookmarkableUrl(), equalTo("/user/1989/ufuk-uzun")); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/test/java/com/expercise/interpreter/InfiniteLoopTestInterpreter.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 4 | 5 | public class InfiniteLoopTestInterpreter extends Interpreter { 6 | 7 | @Override 8 | protected void interpretInternal(ChallengeEvaluationContext context) { 9 | while (true) { 10 | // Infinite loop 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/interpreter/InterpreterTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.interpreter; 2 | 3 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.Matchers.equalTo; 7 | import static org.junit.Assert.assertFalse; 8 | import static org.junit.Assert.assertThat; 9 | 10 | public class InterpreterTest { 11 | 12 | @Test 13 | public void shouldLimitInterpretationTime() { 14 | InfiniteLoopTestInterpreter interpreter = new InfiniteLoopTestInterpreter(); 15 | 16 | ChallengeEvaluationContext context = new ChallengeEvaluationContext(); 17 | 18 | interpreter.interpret(context); 19 | 20 | assertFalse(context.getInterpreterResult().isSuccess()); 21 | assertThat(context.getInterpreterResult().getFailureType(), equalTo(InterpreterFailureType.NO_RESULT)); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/test/java/com/expercise/service/challenge/action/PreEvaluationExecutorTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.challenge.action; 2 | 3 | import com.expercise.service.challenge.model.ChallengeEvaluationContext; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.runners.MockitoJUnitRunner; 8 | import org.springframework.test.util.ReflectionTestUtils; 9 | 10 | import java.util.Arrays; 11 | 12 | import static org.mockito.Mockito.mock; 13 | import static org.mockito.Mockito.verify; 14 | 15 | @RunWith(MockitoJUnitRunner.class) 16 | public class PreEvaluationExecutorTest { 17 | 18 | @InjectMocks 19 | private PreEvaluationExecutor executor; 20 | 21 | @Test 22 | public void shouldExecutePreActions() { 23 | ChallengeEvaluationContext context = new ChallengeEvaluationContext(); 24 | 25 | PreEvaluationAction mockPreAction1 = mock(PreEvaluationAction.class); 26 | PreEvaluationAction mockPreAction2 = mock(PreEvaluationAction.class); 27 | 28 | ReflectionTestUtils.setField(executor, "preEvaluationActions", Arrays.asList(mockPreAction1, mockPreAction2)); 29 | 30 | executor.execute(context); 31 | 32 | verify(mockPreAction1).execute(context); 33 | verify(mockPreAction2).execute(context); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/service/language/JavaScriptSignatureGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.language; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.ChallengeInputType; 5 | import com.expercise.enums.DataType; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.hamcrest.Matchers.equalTo; 15 | import static org.junit.Assert.assertThat; 16 | 17 | @RunWith(MockitoJUnitRunner.class) 18 | public class JavaScriptSignatureGeneratorTest { 19 | 20 | @InjectMocks 21 | private JavaScriptSignatureGenerator signatureGenerator; 22 | 23 | @Test 24 | public void shouldGeneratePatternWithoutParameters() { 25 | Challenge challenge = new Challenge(); 26 | 27 | assertThat(signatureGenerator.generate(challenge), equalTo("function solution() {\n\t\n}")); 28 | } 29 | 30 | @Test 31 | public void shouldGeneratePatternWithMultipleParameters() { 32 | Challenge challenge = new Challenge(); 33 | 34 | List inputTypes = new ArrayList<>(); 35 | inputTypes.add(DataType.Integer); 36 | inputTypes.add(DataType.Integer); 37 | challenge.setInputTypes(ChallengeInputType.createFrom(inputTypes)); 38 | challenge.setOutputType(DataType.Integer); 39 | 40 | assertThat(signatureGenerator.generate(challenge), equalTo("function solution(a, b) {\n\t\n}")); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/service/language/JavaSignatureGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.language; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.ChallengeInputType; 5 | import com.expercise.enums.DataType; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.hamcrest.Matchers.equalTo; 15 | import static org.junit.Assert.assertThat; 16 | 17 | @RunWith(MockitoJUnitRunner.class) 18 | public class JavaSignatureGeneratorTest { 19 | 20 | @InjectMocks 21 | private JavaSignatureGenerator signatureGenerator; 22 | 23 | @Test 24 | public void shouldGeneratePatternWithoutParameters() { 25 | Challenge challenge = new Challenge(); 26 | challenge.setOutputType(DataType.Integer); 27 | 28 | assertThat(signatureGenerator.generate(challenge), equalTo("public class Solution {\n\n\tpublic Integer solution() {\n\t\t\n\t}\n\n}")); 29 | } 30 | 31 | @Test 32 | public void shouldGeneratePatternWithMultipleParameters() { 33 | Challenge challenge = new Challenge(); 34 | 35 | List inputTypes = new ArrayList<>(); 36 | inputTypes.add(DataType.Integer); 37 | inputTypes.add(DataType.Text); 38 | challenge.setInputTypes(ChallengeInputType.createFrom(inputTypes)); 39 | challenge.setOutputType(DataType.Integer); 40 | 41 | assertThat(signatureGenerator.generate(challenge), equalTo("public class Solution {\n\n\tpublic Integer solution(Integer a, String b) {\n\t\t\n\t}\n\n}")); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/test/java/com/expercise/service/language/Python2SignatureGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.language; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.ChallengeInputType; 5 | import com.expercise.enums.DataType; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.hamcrest.Matchers.equalTo; 15 | import static org.junit.Assert.assertThat; 16 | 17 | @RunWith(MockitoJUnitRunner.class) 18 | public class Python2SignatureGeneratorTest { 19 | 20 | @InjectMocks 21 | private Python2SignatureGenerator signatureGenerator; 22 | 23 | @Test 24 | public void shouldGeneratePatternWithoutParameters() { 25 | Challenge challenge = new Challenge(); 26 | 27 | assertThat(signatureGenerator.generate(challenge), equalTo("def solution():\n\t")); 28 | } 29 | 30 | @Test 31 | public void shouldGeneratePatternWithMultipleParameters() { 32 | Challenge challenge = new Challenge(); 33 | 34 | List inputTypes = new ArrayList<>(); 35 | inputTypes.add(DataType.Integer); 36 | inputTypes.add(DataType.Integer); 37 | challenge.setInputTypes(ChallengeInputType.createFrom(inputTypes)); 38 | challenge.setOutputType(DataType.Integer); 39 | 40 | assertThat(signatureGenerator.generate(challenge), equalTo("def solution(a, b):\n\t")); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/service/user/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.service.user; 2 | 3 | import com.expercise.domain.user.User; 4 | import com.expercise.enums.Lingo; 5 | import com.expercise.repository.user.UserRepository; 6 | import com.expercise.service.notification.SlackNotificationService; 7 | import com.expercise.service.util.UrlService; 8 | import com.expercise.testutils.builder.UserBuilder; 9 | import com.expercise.utils.PasswordEncoder; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.ArgumentCaptor; 13 | import org.mockito.InjectMocks; 14 | import org.mockito.Mock; 15 | import org.mockito.runners.MockitoJUnitRunner; 16 | 17 | import static org.hamcrest.CoreMatchers.equalTo; 18 | import static org.hamcrest.MatcherAssert.assertThat; 19 | import static org.mockito.Mockito.verify; 20 | import static org.mockito.Mockito.when; 21 | 22 | @RunWith(MockitoJUnitRunner.class) 23 | public class UserServiceTest { 24 | 25 | @InjectMocks 26 | private UserService userService; 27 | 28 | @Mock 29 | private UserRepository userRepository; 30 | 31 | @Mock 32 | private PasswordEncoder passwordEncoder; 33 | 34 | @Mock 35 | private UrlService urlService; 36 | 37 | @Mock 38 | private SlackNotificationService slackNotificationService; 39 | 40 | @Test 41 | public void shouldSaveNewUserWithHashedPassword() { 42 | User user = new UserBuilder().firstName("mahmut").lastName("kemal") 43 | .email("mail@expercise.com").lingo(Lingo.Turkish).password("password") 44 | .buildWithRandomId(); 45 | 46 | when(passwordEncoder.encode("password")).thenReturn("hashedPassword"); 47 | 48 | userService.saveNewUser(user); 49 | 50 | ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); 51 | verify(userRepository).save(userCaptor.capture()); 52 | 53 | assertThat(user.getPassword(), equalTo("hashedPassword")); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/FileTestUtils.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils; 2 | 3 | import com.expercise.exception.ExperciseGenericException; 4 | import org.apache.commons.io.IOUtils; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | public final class FileTestUtils { 10 | 11 | private FileTestUtils() { 12 | } 13 | 14 | public static String getFileContentFrom(String file) { 15 | InputStream inputStream = FileTestUtils.class.getResourceAsStream(file); 16 | try { 17 | return IOUtils.toString(inputStream); 18 | } catch (IOException e) { 19 | throw new ExperciseGenericException("File couldn't be read: " + file, e); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/asserts/Asserts.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.asserts; 2 | 3 | import java.util.List; 4 | 5 | import static com.expercise.testutils.matchers.Matchers.notHasItem; 6 | import static org.hamcrest.Matchers.*; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public final class Asserts { 10 | 11 | private Asserts() { 12 | } 13 | 14 | @SafeVarargs 15 | public static void assertExpectedItems(List resultList, T... expectedItems) { 16 | assertThat(resultList, hasSize(expectedItems.length)); 17 | assertThat(resultList, hasItems(expectedItems)); 18 | } 19 | 20 | @SafeVarargs 21 | public static void assertNotExpectedItems(List resultList, T... notExpectedItems) { 22 | for (T each : notExpectedItems) { 23 | assertThat(resultList, notHasItem(each)); 24 | } 25 | } 26 | 27 | @SafeVarargs 28 | public static void assertOrderedItems(List resultList, T... orderedItems) { 29 | assertThat(resultList, hasSize(orderedItems.length)); 30 | for (int index = 0; index < orderedItems.length; index++) { 31 | assertThat(resultList.get(index), equalTo(orderedItems[index])); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/BaseEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.BaseEntity; 4 | 5 | import javax.persistence.EntityManager; 6 | import java.util.Random; 7 | 8 | public abstract class BaseEntityBuilder { 9 | 10 | private Long id; 11 | 12 | protected abstract T doBuild(); 13 | 14 | public T build() { 15 | T entity = doBuild(); 16 | entity.setId(id); 17 | return entity; 18 | } 19 | 20 | public T buildWithRandomId() { 21 | T entity = build(); 22 | entity.setId(new Random().nextLong()); 23 | return entity; 24 | } 25 | 26 | public B id(Long id) { 27 | this.id = id; 28 | return (B) this; 29 | } 30 | 31 | public T persist(EntityManager entityManager) { 32 | T entity = build(); 33 | entityManager.persist(entity); 34 | return entity; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/BasePrioritizedEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.PrioritizedEntity; 4 | 5 | public abstract class BasePrioritizedEntityBuilder extends BaseEntityBuilder { 6 | 7 | private int priority; 8 | 9 | protected abstract T getInstance(); 10 | 11 | @Override 12 | protected T doBuild() { 13 | T entity = getInstance(); 14 | entity.setPriority(priority); 15 | return entity; 16 | } 17 | 18 | public B priority(int priority) { 19 | this.priority = priority; 20 | return (B) this; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/ChallengeInputTypeBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.ChallengeInputType; 5 | import com.expercise.enums.DataType; 6 | 7 | public class ChallengeInputTypeBuilder extends BasePrioritizedEntityBuilder { 8 | 9 | private DataType inputType; 10 | 11 | private Challenge challenge; 12 | 13 | @Override 14 | protected ChallengeInputType getInstance() { 15 | ChallengeInputType challengeInputType = new ChallengeInputType(); 16 | challengeInputType.setChallenge(challenge); 17 | challengeInputType.setInputType(inputType); 18 | challenge.getInputTypes().add(challengeInputType); 19 | return challengeInputType; 20 | } 21 | 22 | public ChallengeInputTypeBuilder inputType(DataType inputType) { 23 | this.inputType = inputType; 24 | return this; 25 | } 26 | 27 | public ChallengeInputTypeBuilder challenge(Challenge challenge) { 28 | this.challenge = challenge; 29 | return this; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/QuoteBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.quote.Quote; 4 | 5 | public class QuoteBuilder extends BaseEntityBuilder { 6 | 7 | @Override 8 | protected Quote doBuild() { 9 | return new Quote(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/SolutionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.Solution; 5 | import com.expercise.domain.user.User; 6 | import com.expercise.enums.ProgrammingLanguage; 7 | import com.expercise.utils.TextUtils; 8 | 9 | import java.util.Date; 10 | 11 | public class SolutionBuilder extends BaseEntityBuilder { 12 | 13 | private Challenge challenge; 14 | 15 | private User user; 16 | 17 | private ProgrammingLanguage programmingLanguage = ProgrammingLanguage.Python2; 18 | 19 | private String solution = TextUtils.randomAlphabetic(10); 20 | 21 | private Date createDate = new Date(); 22 | 23 | @Override 24 | protected Solution doBuild() { 25 | Solution newSolution = new Solution(); 26 | newSolution.setChallenge(challenge); 27 | newSolution.setUser(user); 28 | newSolution.setProgrammingLanguage(programmingLanguage); 29 | newSolution.setSolution(solution); 30 | newSolution.setCreateDate(createDate); 31 | return newSolution; 32 | } 33 | 34 | public SolutionBuilder challenge(Challenge challenge) { 35 | this.challenge = challenge; 36 | return this; 37 | } 38 | 39 | public SolutionBuilder user(User user) { 40 | this.user = user; 41 | return this; 42 | } 43 | 44 | public SolutionBuilder programmingLanguage(ProgrammingLanguage programmingLanguage) { 45 | this.programmingLanguage = programmingLanguage; 46 | return this; 47 | } 48 | 49 | public SolutionBuilder solution(String solution) { 50 | this.solution = solution; 51 | return this; 52 | } 53 | 54 | public SolutionBuilder createDate(Date createDate) { 55 | this.createDate = createDate; 56 | return this; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/TestCaseBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.TestCase; 5 | import com.expercise.domain.challenge.TestCaseInputValue; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | public class TestCaseBuilder extends BasePrioritizedEntityBuilder { 12 | 13 | private Challenge challenge; 14 | 15 | private List inputs = new ArrayList<>(); 16 | 17 | private String output; 18 | 19 | @Override 20 | protected TestCase getInstance() { 21 | TestCase testCase = new TestCase(); 22 | challenge.addTestCase(testCase); 23 | inputs.stream().forEach(input -> { 24 | testCase.getInputs().add(input); 25 | input.setTestCase(testCase); 26 | }); 27 | testCase.setOutput(output); 28 | return testCase; 29 | } 30 | 31 | public TestCaseBuilder challenge(Challenge challenge) { 32 | this.challenge = challenge; 33 | return this; 34 | } 35 | 36 | public TestCaseBuilder inputs(TestCaseInputValue... inputsValues) { 37 | Collections.addAll(inputs, inputsValues); 38 | return this; 39 | } 40 | 41 | public TestCaseBuilder output(String output) { 42 | this.output = output; 43 | return this; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/TestCaseInputValueBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.challenge.TestCase; 4 | import com.expercise.domain.challenge.TestCaseInputValue; 5 | 6 | public class TestCaseInputValueBuilder extends BasePrioritizedEntityBuilder { 7 | 8 | private String inputValue; 9 | 10 | private TestCase testCase; 11 | 12 | @Override 13 | protected TestCaseInputValue getInstance() { 14 | TestCaseInputValue testCaseInputValue = new TestCaseInputValue(); 15 | testCaseInputValue.setInputValue(inputValue); 16 | testCaseInputValue.setTestCase(testCase); 17 | return testCaseInputValue; 18 | } 19 | 20 | public TestCaseInputValueBuilder inputValue(String inputValue) { 21 | this.inputValue = inputValue; 22 | return this; 23 | } 24 | 25 | public TestCaseInputValueBuilder testCase(TestCase testCase) { 26 | this.testCase = testCase; 27 | return this; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/UserBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.user.User; 4 | import com.expercise.enums.Lingo; 5 | import com.expercise.enums.UserRole; 6 | import com.expercise.utils.TextUtils; 7 | 8 | public class UserBuilder extends BaseEntityBuilder { 9 | 10 | private String firstName = TextUtils.randomAlphabetic(5); 11 | 12 | private String lastName = TextUtils.randomAlphabetic(5); 13 | 14 | private String email = TextUtils.randomAlphabetic(5); 15 | 16 | private String password = TextUtils.randomAlphabetic(5); 17 | 18 | private UserRole userRole = UserRole.User; 19 | 20 | private Lingo lingo = Lingo.English; 21 | 22 | @Override 23 | protected User doBuild() { 24 | User user = new User(); 25 | user.setFirstName(firstName); 26 | user.setLastName(lastName); 27 | user.setPassword(password); 28 | user.setEmail(password); 29 | user.setUserRole(userRole); 30 | user.setLingo(lingo); 31 | user.setEmail(email); 32 | return user; 33 | } 34 | 35 | public UserBuilder firstName(String firstName) { 36 | this.firstName = firstName; 37 | return this; 38 | } 39 | 40 | public UserBuilder lastName(String lastName) { 41 | this.lastName = lastName; 42 | return this; 43 | } 44 | 45 | public UserBuilder email(String email) { 46 | this.email = email; 47 | return this; 48 | } 49 | 50 | public UserBuilder password(String password) { 51 | this.password = password; 52 | return this; 53 | } 54 | 55 | public UserBuilder userRole(UserRole userRole) { 56 | this.userRole = userRole; 57 | return this; 58 | } 59 | 60 | public UserBuilder lingo(Lingo lingo) { 61 | this.lingo = lingo; 62 | return this; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/builder/UserPointBuilder.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.builder; 2 | 3 | import com.expercise.domain.challenge.Challenge; 4 | import com.expercise.domain.challenge.UserPoint; 5 | import com.expercise.domain.user.User; 6 | import com.expercise.enums.ProgrammingLanguage; 7 | 8 | import java.util.Date; 9 | 10 | public class UserPointBuilder extends BaseEntityBuilder { 11 | 12 | private User user; 13 | 14 | private Challenge challenge; 15 | 16 | private ProgrammingLanguage programmingLanguage; 17 | 18 | private int pointAmount; 19 | 20 | private Date givenDate; 21 | 22 | @Override 23 | protected UserPoint doBuild() { 24 | UserPoint userPoint = new UserPoint(); 25 | userPoint.setUser(user); 26 | userPoint.setChallenge(challenge); 27 | userPoint.setProgrammingLanguage(programmingLanguage); 28 | userPoint.setPointAmount(pointAmount); 29 | userPoint.setGivenDate(givenDate); 30 | return userPoint; 31 | } 32 | 33 | public UserPointBuilder user(User user) { 34 | this.user = user; 35 | return this; 36 | } 37 | 38 | public UserPointBuilder challenge(Challenge challenge) { 39 | this.challenge = challenge; 40 | return this; 41 | } 42 | 43 | public UserPointBuilder programmingLanguage(ProgrammingLanguage programmingLanguage) { 44 | this.programmingLanguage = programmingLanguage; 45 | return this; 46 | } 47 | 48 | public UserPointBuilder pointAmount(int pointAmount) { 49 | this.pointAmount = pointAmount; 50 | return this; 51 | } 52 | 53 | public UserPointBuilder givenDate(Date givenDate) { 54 | this.givenDate = givenDate; 55 | return this; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/testutils/matchers/Matchers.java: -------------------------------------------------------------------------------- 1 | package com.expercise.testutils.matchers; 2 | 3 | import org.hamcrest.Matcher; 4 | 5 | import static org.hamcrest.Matchers.hasItem; 6 | import static org.hamcrest.Matchers.not; 7 | 8 | public final class Matchers { 9 | 10 | private Matchers() { 11 | } 12 | 13 | public static Matcher> notHasItem(T item) { 14 | return not(hasItem(item)); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/expercise/utils/CookieUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.runners.MockitoJUnitRunner; 8 | 9 | import javax.servlet.http.Cookie; 10 | import javax.servlet.http.HttpServletRequest; 11 | import java.util.Optional; 12 | 13 | import static org.hamcrest.Matchers.equalTo; 14 | import static org.junit.Assert.assertThat; 15 | import static org.mockito.Mockito.when; 16 | 17 | @RunWith(MockitoJUnitRunner.class) 18 | public class CookieUtilsTest { 19 | 20 | @InjectMocks 21 | private CookieUtils cookieUtils; 22 | 23 | @Mock 24 | private HttpServletRequest request; 25 | 26 | @Test 27 | public void shouldGetNullCookieValueForFirstVisitingOfUser() { 28 | Optional cookieValue = cookieUtils.getCookieValue("cookieName"); 29 | 30 | assertThat(cookieValue.isPresent(), equalTo(false)); 31 | } 32 | 33 | @Test 34 | public void shouldGetCookieValueIfExist() { 35 | when(request.getCookies()).thenReturn(new Cookie[]{new Cookie("cookieName", "cookieValue")}); 36 | 37 | Optional cookieValue = cookieUtils.getCookieValue("cookieName"); 38 | 39 | assertThat(cookieValue.get(), equalTo("cookieValue")); 40 | } 41 | 42 | @Test 43 | public void shouldGetNullCookieValueIfNotExist() { 44 | when(request.getCookies()).thenReturn(new Cookie[]{new Cookie("cookieName", "cookieValue")}); 45 | 46 | Optional cookieValue = cookieUtils.getCookieValue("anotherCookieName"); 47 | 48 | assertThat(cookieValue.isPresent(), equalTo(false)); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/test/java/com/expercise/utils/JsonUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.expercise.utils; 2 | 3 | import com.expercise.exception.ExperciseJsonException; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | import java.util.List; 8 | 9 | import static org.hamcrest.CoreMatchers.equalTo; 10 | import static org.junit.Assert.assertThat; 11 | import static org.junit.Assert.fail; 12 | 13 | public class JsonUtilsTest { 14 | 15 | @Test 16 | public void shouldFormatJsonStringProperlyAccordingToItsType() throws IOException { 17 | assertThat(JsonUtils.formatSafely(" 1 ", Integer.class), equalTo("1")); 18 | assertThat(JsonUtils.formatSafely(" \"1\" ", String.class), equalTo("\"1\"")); 19 | assertThat(JsonUtils.formatSafely(" [ \"1\", 2 ,3, [ 2, \"19\" ] ]", List.class), equalTo("[\"1\",2,3,[2,\"19\"]]")); 20 | } 21 | 22 | @Test 23 | public void shouldThrowJsonProcessingExceptionIfInputIsInvalidJsonType() { 24 | assertInvalidJsonFormat("\"1"); 25 | assertInvalidJsonFormat("1\""); 26 | assertInvalidJsonFormat("[1\""); 27 | assertInvalidJsonFormat("1]"); 28 | assertInvalidJsonFormat(null); 29 | } 30 | 31 | private void assertInvalidJsonFormat(String input) { 32 | try { 33 | JsonUtils.formatSafely(input, Object.class); 34 | fail(); 35 | } catch (ExperciseJsonException ignored) { 36 | } catch (Exception e) { 37 | fail(); 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Spring Boot 2 | spring.profiles.active=test 3 | 4 | spring.redis.port=6379 5 | 6 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 7 | spring.jpa.hibernate.ddl-auto=create-drop 8 | spring.jpa.generate-ddl=true 9 | spring.datasource.platform=H2 10 | spring.datasource.url=jdbc:h2:mem:codingexpercise:H2;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS PUBLIC 11 | spring.datasource.driverClassName=org.h2.Driver 12 | spring.datasource.initialize=true 13 | spring.datasource.username=sa 14 | spring.datasource.password= 15 | 16 | liquibase.enabled=false 17 | 18 | # Coding Expercise 19 | coding-expercise.root-url=http://localhost:8080 20 | coding-expercise.email-status=deactive 21 | coding-expercise.send-grid.username= 22 | coding-expercise.send-grid.password= 23 | coding-expercise.twitter.consumer-key= 24 | coding-expercise.twitter.consumer-secret= 25 | coding-expercise.challenge-approval-strategy=auto 26 | coding-expercise.googleAnalytics.applicationKey= 27 | coding-expercise.slack.incomingWebhookUrl= 28 | coding-expercise.defaults.challengeId=-1 29 | coding-expercise.interpreter.api.url=http://localhost:9119 30 | 31 | build.id=-1 32 | -------------------------------------------------------------------------------- /src/test/resources/renderedEmails/forgotMyPasswordEmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Hello Ahmet Mehmet,

7 |

Please click link below to reset your password.

8 | Reset Password 9 | 10 | 11 | --------------------------------------------------------------------------------