├── src ├── main │ ├── resources │ │ ├── static │ │ │ ├── robots.txt │ │ │ ├── favicon.ico │ │ │ ├── og-image.jpg │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── mstile-150x150.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-96x96.png │ │ │ ├── fonts │ │ │ │ ├── roboto-bold-webfont.woff │ │ │ │ ├── roboto-bold-webfont.woff2 │ │ │ │ ├── roboto-italic-webfont.woff │ │ │ │ ├── roboto-medium-webfont.woff │ │ │ │ ├── roboto-italic-webfont.woff2 │ │ │ │ ├── roboto-medium-webfont.woff2 │ │ │ │ ├── roboto-regular-webfont.woff │ │ │ │ ├── roboto-regular-webfont.woff2 │ │ │ │ ├── roboto-bolditalic-webfont.woff │ │ │ │ ├── roboto-bolditalic-webfont.woff2 │ │ │ │ ├── roboto-mediumitalic-webfont.woff │ │ │ │ └── roboto-mediumitalic-webfont.woff2 │ │ │ ├── browserconfig.xml │ │ │ ├── manifest.json │ │ │ ├── images │ │ │ │ ├── the-example-app-logo-dotnet.svg │ │ │ │ ├── icon-javascript.svg │ │ │ │ ├── icon-python.svg │ │ │ │ ├── icon-nodejs.svg │ │ │ │ ├── icon-java.svg │ │ │ │ ├── icon-swift.svg │ │ │ │ ├── icon-php.svg │ │ │ │ ├── icon-android.svg │ │ │ │ └── contentful-logo.svg │ │ │ ├── safari-pinned-tab.svg │ │ │ └── icons │ │ │ │ └── icons.svg │ │ ├── templates │ │ │ ├── mixins │ │ │ │ ├── _lessonModuleCopy.jade │ │ │ │ ├── _entryState.jade │ │ │ │ ├── _breadcrumb.jade │ │ │ │ ├── _moduleHeroImage.jade │ │ │ │ ├── _lessonModuleImage.jade │ │ │ │ ├── _moduleCopy.jade │ │ │ │ ├── _courseCard.jade │ │ │ │ ├── _editorialFeatures.jade │ │ │ │ ├── _moduleHighlightedCourse.jade │ │ │ │ ├── _lesson.jade │ │ │ │ └── _lessonModuleCodeSnippet.jade │ │ │ ├── error.jade │ │ │ ├── imprint.jade │ │ │ ├── landingPage.jade │ │ │ ├── courses.jade │ │ │ ├── course.jade │ │ │ └── settings.jade │ │ └── application.properties │ └── java │ │ └── com │ │ └── contentful │ │ └── tea │ │ └── java │ │ ├── markdown │ │ ├── MarkdownParser.java │ │ └── CommonmarkMarkdownParser.java │ │ ├── models │ │ ├── mappable │ │ │ ├── NullHandler.java │ │ │ ├── utilities │ │ │ │ └── Reflection.java │ │ │ └── MappableType.java │ │ ├── exceptions │ │ │ └── RedirectException.java │ │ ├── landing │ │ │ ├── modules │ │ │ │ ├── BaseModule.java │ │ │ │ ├── HighlightedCourseModule.java │ │ │ │ ├── HeroImageModule.java │ │ │ │ └── CopyModule.java │ │ │ └── LandingPageParameter.java │ │ ├── courses │ │ │ ├── lessons │ │ │ │ ├── modules │ │ │ │ │ ├── CopyModule.java │ │ │ │ │ ├── Module.java │ │ │ │ │ ├── ImageModule.java │ │ │ │ │ └── CodeModule.java │ │ │ │ └── LessonParameter.java │ │ │ ├── Category.java │ │ │ ├── CoursesParameter.java │ │ │ └── CourseParameter.java │ │ ├── base │ │ │ ├── AnalyticsParameter.java │ │ │ ├── Locale.java │ │ │ ├── BaseParameter.java │ │ │ ├── ApiParameter.java │ │ │ ├── LocalesParameter.java │ │ │ └── BreadcrumbParameter.java │ │ ├── imprint │ │ │ └── ImprintParameter.java │ │ └── errors │ │ │ └── ErrorParameter.java │ │ ├── services │ │ ├── http │ │ │ ├── Constants.java │ │ │ └── SessionParser.java │ │ ├── modelconverter │ │ │ ├── ContentfulModelToMappableTypeConverter.java │ │ │ ├── EntryToLesson.java │ │ │ ├── ExceptionToErrorParameter.java │ │ │ └── EntryToLandingPage.java │ │ ├── modelcreators │ │ │ └── ImprintCreator.java │ │ ├── localization │ │ │ ├── Localizer.java │ │ │ └── Keys.java │ │ ├── settings │ │ │ └── Settings.java │ │ └── modelenhancers │ │ │ └── EditorialFeaturesEnhancer.java │ │ ├── html │ │ └── JadeHtmlGenerator.java │ │ └── SecurityConfig.java └── test │ ├── resources │ ├── jade │ │ └── input.jade │ ├── markdown │ │ ├── simple.md │ │ └── complex.md │ ├── error │ │ └── main.json │ ├── _scripts │ │ ├── defaults │ │ │ ├── space.sh │ │ │ └── content_types.sh │ │ ├── models │ │ │ ├── courses.sh │ │ │ └── categories.sh │ │ ├── courses │ │ │ ├── all.sh │ │ │ ├── one_category.sh │ │ │ ├── one.sh │ │ │ └── one_slug_and_title_differ.sh │ │ ├── error │ │ │ └── main.sh │ │ ├── markdown │ │ │ ├── simple.sh │ │ │ └── complex.sh │ │ ├── lessons │ │ │ └── complete.sh │ │ ├── home │ │ │ └── main.sh │ │ ├── e2e.sh │ │ └── all.sh │ ├── defaults │ │ ├── space.json │ │ └── locales.json │ └── models │ │ └── categories.json │ └── java │ └── com │ └── contentful │ └── tea │ └── java │ ├── utils │ ├── text │ │ ├── InjectText.java │ │ ├── InjectTextBaseTests.java │ │ └── InjectTextRule.java │ ├── http │ │ ├── ComplexHttpResponse.java │ │ ├── EnqueueHttpResponse.java │ │ ├── HttpResponse.java │ │ ├── EnqueueHttpResponseRule.java │ │ └── EnqueuedHttpResponseTests.java │ └── TestUtils.java │ ├── services │ ├── localization │ │ └── LocalizerTests.java │ ├── http │ │ ├── NonInjectedUrlParameterParserTest.java │ │ ├── UrlParameterParserTest.java │ │ ├── SessionParserTest.java │ │ └── UrlParserTest.java │ ├── modelenhancers │ │ └── EditorialFeaturesEnhancerTest.java │ ├── settings │ │ └── SettingsTest.java │ ├── modelconverter │ │ └── ExceptionToErrorParameterTest.java │ └── contentful │ │ └── ContentfulTests.java │ ├── HtmlGeneratorTests.java │ ├── MarkdownManipulatorTests.java │ ├── MappableTypeTests.java │ └── models │ └── LessonsModelTests.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── .gitignore ├── cypress-circleci.json ├── cypress.json ├── .circleci └── config.yml ├── gradlew.bat └── README.md /src/main/resources/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /src/test/resources/jade/input.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | body 6 | p #{text} 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/og-image.jpg -------------------------------------------------------------------------------- /src/main/resources/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/favicon-16x16.png -------------------------------------------------------------------------------- /src/main/resources/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/favicon-32x32.png -------------------------------------------------------------------------------- /src/main/resources/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/mstile-150x150.png -------------------------------------------------------------------------------- /src/main/resources/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/apple-touch-icon.png -------------------------------------------------------------------------------- /src/test/resources/markdown/simple.md: -------------------------------------------------------------------------------- 1 | Switch to the language from English to German by going to the menu item Locale: `U.S. English` and selecting `German`. 2 | -------------------------------------------------------------------------------- /src/main/resources/static/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/android-chrome-96x96.png -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-bold-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-bold-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-italic-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-medium-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-medium-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_lessonModuleCopy.jade: -------------------------------------------------------------------------------- 1 | mixin lessonModuleCopy(module) 2 | .lesson-module.lesson-module-copy 3 | .lesson-module-copy__copy !{module.copy} 4 | -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-italic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-italic-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-medium-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-medium-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-regular-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-regular-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-bolditalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-bolditalic-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-bolditalic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-bolditalic-webfont.woff2 -------------------------------------------------------------------------------- /src/test/resources/error/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [], 3 | "limit": 100, 4 | "skip": 0, 5 | "sys": { 6 | "type": "Array" 7 | }, 8 | "total": 0 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/markdown/MarkdownParser.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.markdown; 2 | 3 | public interface MarkdownParser { 4 | String parse(String toParse); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-mediumitalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-mediumitalic-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/roboto-mediumitalic-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/the-example-app.java/HEAD/src/main/resources/static/fonts/roboto-mediumitalic-webfont.woff2 -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/mappable/NullHandler.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.mappable; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public interface NullHandler { 6 | Object handleNull(Field field); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_entryState.jade: -------------------------------------------------------------------------------- 1 | mixin entryState(draft, pending) 2 | if draft 3 | .pill.pill--draft #{base.labels.draftLabel} 4 | if pending 5 | .pill.pill--pending-changes #{base.labels.pendingChangesLabel} 6 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | spaceId=qz0n5cdakyl9 2 | deliveryToken=580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8 3 | previewToken=e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b 4 | version=1.0.0 5 | application=The example java app -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spaceId=qz0n5cdakyl9 2 | deliveryToken=580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8 3 | previewToken=e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b 4 | version=1.0.0 5 | application=The example java app -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 18 09:52:04 CET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-all.zip 7 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_breadcrumb.jade: -------------------------------------------------------------------------------- 1 | mixin breadcrumb 2 | if base.breadcrumb 3 | nav.breadcrumb 4 | ul 5 | each breadcrumb in base.breadcrumb.breadcrumbs 6 | li 7 | a(href='!{breadcrumb.url}!{base.meta.queryString}') #{breadcrumb.label} 8 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/defaults/space.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID} \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/main/resources/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffffff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/defaults/content_types.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/content_types' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/models/courses.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?content_type=course' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/models/categories.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?content_type=category' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/courses/all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?content_type=course&include=5' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/error/main.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?sys.id=does_not_exist&include=5' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/courses/one_category.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?content_type=course&include=5' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/main/resources/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "The Example App", 3 | "icons": [ 4 | { 5 | "src": "/android-chrome-96x96.png", 6 | "sizes": "96x96", 7 | "type": "image/png" 8 | } 9 | ], 10 | "theme_color": "#ffffff", 11 | "background_color": "#ffffff", 12 | "display": "standalone" 13 | } -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_moduleHeroImage.jade: -------------------------------------------------------------------------------- 1 | mixin moduleHeroImage(module) 2 | .module.module-hero-image 3 | .module-hero-image__wrapper 4 | .module-hero-image__headline-wrapper 5 | h2.module-hero-image__headline #{module.headline} 6 | img.module-hero-image__image(src='!{module.backgroundImageUrl}' alt='!{module.backgroundImageTitle}') 7 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/courses/one.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?sys.id=34MlmiuMgU8wKCOOIkAuMy&include=2' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/markdown/simple.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#md#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?sys.id=58B62ubQbmIOiAkmq44kAo' \ 8 | | jq -r .items[0].fields.copy \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/lessons/complete.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?sys.id=2SAYsnajosIkCOWqSmKaio&include=2' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/courses/one_slug_and_title_differ.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?sys.id=22T0z4Abr6SeksycEwsi4o' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/home/main.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#json#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CPA_TOKEN} \ 7 | 'https://preview.contentful.com/spaces/'${SPACE_ID}'/entries?sys.id=2uNOpLMJioKeoMq8W44uYc&include=5&locale=*' \ 8 | | python -m json.tool \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/exceptions/RedirectException.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.exceptions; 2 | 3 | public class RedirectException extends RuntimeException { 4 | private final String whereTo; 5 | 6 | public RedirectException(String whereTo) { 7 | this.whereTo = whereTo; 8 | } 9 | 10 | public String getWhereTo() { 11 | return whereTo; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/markdown/complex.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT="$(echo $0 | sed 's#sh#md#g' | sed 's#^#../#g')" 4 | 5 | curl --silent \ 6 | -H 'Authorization: Bearer '${CDA_TOKEN} \ 7 | 'https://cdn.contentful.com/spaces/'${SPACE_ID}'/entries?content_type=lessonCopy&select=fields.copy&sys.id=1AqQQzRvKAkEOekuMUcQIY' \ 8 | | jq -r .items[0].fields.copy \ 9 | > ${OUTPUT} 10 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/text/InjectText.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils.text; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface InjectText { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_lessonModuleImage.jade: -------------------------------------------------------------------------------- 1 | mixin lessonModuleImage(module) 2 | .lesson-module.lesson-module-image 3 | if module.imageUrl 4 | figure.lesson-module-image__figure 5 | img.lesson-module-image__image(src=module.imageUrl alt=module.title) 6 | if module.caption 7 | figcaption.lesson-module-image__caption= module.caption 8 | else 9 | h3 #{module.missingImageLabel} 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.ear 16 | *.zip 17 | *.tar.gz 18 | *.rar 19 | 20 | # IntelliJ IDEA 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | 26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | out/ 29 | cypress/ 30 | .gradle 31 | build/ 32 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/text/InjectTextBaseTests.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils.text; 2 | 3 | import com.contentful.tea.java.MainController; 4 | 5 | import org.junit.Rule; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | @SpringBootTest(classes = MainController.class) 9 | public class InjectTextBaseTests { 10 | @Rule public InjectTextRule injectTextRule = new InjectTextRule(); 11 | 12 | protected String injectedText; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/http/ComplexHttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils.http; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ComplexHttpResponse { 11 | String fileName(); 12 | 13 | int code() default 200; 14 | 15 | String[] headers() default {}; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/landing/modules/BaseModule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.landing.modules; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | public class BaseModule extends MappableType { 6 | private final String type; 7 | 8 | public BaseModule(String type) { 9 | this.type = type; 10 | } 11 | 12 | @Override public String toString() { 13 | return "BaseModule { " + super.toString() + " " 14 | + "type = " + type + " " 15 | + "}"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/resources/defaults/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "locales": [ 3 | { 4 | "code": "de-DE", 5 | "default": false, 6 | "fallbackCode": "en-US", 7 | "name": "German (Germany)" 8 | }, 9 | { 10 | "code": "en-US", 11 | "default": true, 12 | "fallbackCode": null, 13 | "name": "U.S. English" 14 | } 15 | ], 16 | "name": "[QA] The example app space v1", 17 | "sys": { 18 | "id": "jnzexv31feqf", 19 | "type": "Space" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/http/EnqueueHttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils.http; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface EnqueueHttpResponse { 11 | String[] defaults() default { "defaults/locales.json", "defaults/content_types.json" }; 12 | 13 | String[] value() default {}; 14 | 15 | ComplexHttpResponse[] complex() default {}; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_moduleCopy.jade: -------------------------------------------------------------------------------- 1 | mixin moduleCopy(module) 2 | div(class='module module-copy!{module.style}') 3 | div(class='module-copy__wrapper#{module.style}') 4 | div(class='module-copy__first!{module.style}') 5 | if module.headline 6 | h1(class='module-copy__headline!{module.style}') #{module.headline} 7 | div(class='module-copy__copy!{module.style}') !{module.copy} 8 | div(class='module-copy__second!{module.style}') 9 | if module.ctaTitle && module.ctaLink 10 | a.cta(class='module-copy__cta!{module.style}' href=module.ctaLink)= module.ctaTitle 11 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/mappable/utilities/Reflection.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.mappable.utilities; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | public class Reflection { 8 | public static void findMappableSuperFields(Class superclass, List fields) { 9 | if (!superclass.equals(Object.class)) { 10 | final Field[] superFields = superclass.getDeclaredFields(); 11 | fields.addAll(Arrays.asList(superFields)); 12 | 13 | findMappableSuperFields(superclass.getSuperclass(), fields); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/_scripts/e2e.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$@" ]; then 4 | COMMAND="open"; 5 | else 6 | COMMAND="run"; 7 | fi 8 | 9 | ../the-example-app-e2e-tests/node_modules/.bin/cypress ${COMMAND} --env LANGUAGE=java,CONTENTFUL_QA_SPACE_ID=jnzexv31feqf,CONTENTFUL_QA_DELIVERY_TOKEN=7c1c321a528a25c351c1ac5f53e6ddc6bcce0712ecebec60817f53b35dd3c42b,CONTENTFUL_QA_PREVIEW_TOKEN=4310226db935f0e9b6b34fb9ce6611e2061abe1aab5297fa25bd52af5caa531a,CONTENTFUL_SPACE_ID=qz0n5cdakyl9,CONTENTFUL_DELIVERY_TOKEN=580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8,CONTENTFUL_PREVIEW_TOKEN=e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b -------------------------------------------------------------------------------- /src/test/resources/_scripts/all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This script will be used to regenerate all the canned test files, based on real requests. 4 | # 5 | 6 | export SPACE_ID="jnzexv31feqf" 7 | export CDA_TOKEN="7c1c321a528a25c351c1ac5f53e6ddc6bcce0712ecebec60817f53b35dd3c42b" 8 | export CPA_TOKEN="4310226db935f0e9b6b34fb9ce6611e2061abe1aab5297fa25bd52af5caa531a" 9 | 10 | # loop through all folders 11 | for FILE in *; do 12 | if [[ -d "${FILE}" ]]; then 13 | echo "${FILE}/" 14 | mkdir -p ../${FILE}; 15 | 16 | for SCRIPT in ${FILE}/*.sh; do 17 | echo -n ".. "; 18 | basename ${SCRIPT} .sh; 19 | ${SCRIPT}; 20 | done; 21 | fi; 22 | done -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/http/Constants.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.http; 2 | 3 | public class Constants { 4 | public static final String NAME_API = "api"; 5 | public static final String NAME_SPACE_ID = "space_id"; 6 | public static final String NAME_LOCALE = "locale"; 7 | public static final String NAME_DELIVERY_TOKEN = "delivery_token"; 8 | public static final String NAME_PREVIEW_TOKEN = "preview_token"; 9 | public static final String NAME_EDITORIAL_FEATURES = "editorial_features"; 10 | public static final String EDITORIAL_FEATURES_ENABLED = "enabled"; 11 | public static final String EDITORIAL_FEATURES_DISABLED = "disabled"; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_breadcrumb 4 | 5 | block content 6 | .layout-centered 7 | .error 8 | h1 !{somethingWentWrongLabel} (!{status}) 9 | h2 !{tryLabel} 10 | ul 11 | each hint in hints 12 | li !{hint} 13 | if responseData 14 | h2 !{responseDataLabel} 15 | pre.error__stack-trace 16 | code.shell !{responseData} 17 | if useCustomCredentials 18 | form(action='/settings?reset=true' method='POST') 19 | p 20 | button(type="submit") #{resetCredentialsLabel} 21 | if stack 22 | // 23 | !{stackTraceLabel} 24 | !{stack} 25 | -------------------------------------------------------------------------------- /src/test/resources/defaults/locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 1000, 8 | "items": [ 9 | { 10 | "code": "de-DE", 11 | "default": false, 12 | "fallbackCode": "en-US", 13 | "name": "German (Germany)", 14 | "sys": { 15 | "id": "3zpZmkZrHTIekHmXgflXaV", 16 | "type": "Locale", 17 | "version": 0 18 | } 19 | }, 20 | { 21 | "code": "en-US", 22 | "default": true, 23 | "fallbackCode": null, 24 | "name": "U.S. English", 25 | "sys": { 26 | "id": "2oQPjMCL9bQkylziydLh57", 27 | "type": "Locale", 28 | "version": 1 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/templates/imprint.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_breadcrumb 4 | 5 | block content 6 | .layout-centered 7 | +breadcrumb 8 | h1= title 9 | table 10 | tbody 11 | tr 12 | th #{companyLabel}: 13 | td Contentful GmbH 14 | tr 15 | th #{officeLabel}: 16 | td 17 | | Ritterstr. 12-14 18 | br 19 | | 10969 Berlin 20 | br 21 | | #{germanyLabel} 22 | tr 23 | th #{registrationCourtLabel}: 24 | td Berlin-Charlottenburg HRB 155607 B 25 | tr 26 | th #{managingDirectorLabel}: 27 | td Sascha Konietzke 28 | tr 29 | th #{vatNumberLabel}: 30 | td DE275148225 31 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/localization/LocalizerTests.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.localization; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Java6Assertions.assertThat; 7 | 8 | public class LocalizerTests { 9 | 10 | private Localizer localizer; 11 | 12 | @Before 13 | public void setup() { 14 | localizer = new Localizer(); 15 | } 16 | 17 | @Test 18 | public void basicTranslationWorks() { 19 | assertThat(localizer.localize("en-US", Keys.whatIsThisApp)).isEqualTo("Help"); 20 | assertThat(localizer.localize("de-DE", Keys.whatIsThisApp)).isEqualTo("Hilfe"); 21 | } 22 | 23 | @Test 24 | public void notSupportedLocaleReturnsEnglish() { 25 | assertThat(localizer.localize("non existing locale", Keys.whatIsThisApp)).isEqualTo("Help"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/static/images/the-example-app-logo-dotnet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | example app 11 | 12 | 13 | The .NET 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_courseCard.jade: -------------------------------------------------------------------------------- 1 | include _entryState 2 | 3 | mixin courseCard(course) 4 | .course-card 5 | .course-card__categories 6 | if(course.categories) 7 | each category in course.categories 8 | .course-card__category 9 | a.course-card__category-link(href='/courses/categories/!{category.slug}!{base.meta.queryString}') #{category.title} 10 | h2.course-card__title 11 | a(href='/courses/!{course.slug}!{base.meta.queryString}')= course.title 12 | if (course.draft || course.pendingChanges) 13 | .editorial-features 14 | .editorial-features__item 15 | +entryState(course.draft, course.pendingChanges) 16 | p.course-card__description= course.shortDescription 17 | .course-card__link-wrapper 18 | a.course-card__link(href='/courses/!{course.slug}!{base.meta.queryString}') #{base.labels.viewCourseLabel} 19 | -------------------------------------------------------------------------------- /src/main/resources/static/images/icon-javascript.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/http/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils.http; 2 | 3 | import okhttp3.Headers; 4 | 5 | public class HttpResponse { 6 | private final int code; 7 | private final String fileName; 8 | private final Headers headers; 9 | 10 | HttpResponse(int code, String fileName, String[] headers) { 11 | this.code = code; 12 | this.fileName = fileName; 13 | this.headers = createHeaders(headers); 14 | } 15 | 16 | private Headers createHeaders(String[] headers) { 17 | final Headers.Builder builder = new Headers.Builder(); 18 | for (final String line : headers) { 19 | builder.add(line); 20 | } 21 | return builder.build(); 22 | } 23 | 24 | public int getCode() { 25 | return code; 26 | } 27 | 28 | public String getFileName() { 29 | return fileName; 30 | } 31 | 32 | public Headers headers() { 33 | return headers; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_editorialFeatures.jade: -------------------------------------------------------------------------------- 1 | include _entryState 2 | 3 | mixin editorialFeatures(item) 4 | if base.meta.editorialFeatures 5 | .editorial-features 6 | if item.base.meta.draft || item.base.meta.pendingChanges 7 | .editorial-features__item 8 | +entryState(item.base.meta.draft, item.base.meta.pendingChanges) 9 | if item.draft || item.pendingChanges 10 | .editorial-features__item 11 | +entryState(item.draft, item.pendingChanges) 12 | .editorial-features__item 13 | a.editorial-features__text( 14 | href='!{base.meta.deeplinkToContentful}' 15 | target='_blank' 16 | rel='noopener' 17 | ) #{base.labels.editInWebAppLabel} 18 | .editorial-features__hint-wrapper 19 | svg.editorial-features__hint-icon 20 | use(xlink:href='/icons/icons.svg#info') 21 | .editorial-features__hint-message #{base.labels.editorialFeaturesHint} 22 | -------------------------------------------------------------------------------- /cypress-circleci.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8080", 3 | "fixturesFolder": false, 4 | "integrationFolder": "./test/e2e/specs", 5 | "supportFile": false, 6 | "env": { 7 | "LANGUAGE": "java", 8 | "CONTENTFUL_QA_SPACE_ID": "jnzexv31feqf", 9 | "CONTENTFUL_QA_DELIVERY_TOKEN": "7c1c321a528a25c351c1ac5f53e6ddc6bcce0712ecebec60817f53b35dd3c42b", 10 | "CONTENTFUL_QA_PREVIEW_TOKEN": "4310226db935f0e9b6b34fb9ce6611e2061abe1aab5297fa25bd52af5caa531a", 11 | "CONTENTFUL_SPACE_ID": "qz0n5cdakyl9", 12 | "CONTENTFUL_DELIVERY_TOKEN": "580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8", 13 | "CONTENTFUL_PREVIEW_TOKEN": "e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b", 14 | "CONTENTFUL_QA_EMPTY_STATES_SPACE_ID": "85si70kq8sjj", 15 | "CONTENTFUL_QA_EMPTY_STATES_DELIVERY_TOKEN": "eecf609f1411e10439f95c7d89a86c2661476bc3f92f8f0ead0e303e8e4ce9f9", 16 | "CONTENTFUL_QA_EMPTY_STATES_PREVIEW_TOKEN": "f9d927308234f773f90be351e72010fa6ca8f0e36b94cb3cc0f84a40c43f2e4d" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/static/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 14 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8080", 3 | "fixturesFolder": false, 4 | "integrationFolder": "../the-example-app-e2e-tests/specs", 5 | "supportFile": false, 6 | "env": { 7 | "LANGUAGE": "java", 8 | "CONTENTFUL_QA_SPACE_ID": "jnzexv31feqf", 9 | "CONTENTFUL_QA_DELIVERY_TOKEN": "7c1c321a528a25c351c1ac5f53e6ddc6bcce0712ecebec60817f53b35dd3c42b", 10 | "CONTENTFUL_QA_PREVIEW_TOKEN": "4310226db935f0e9b6b34fb9ce6611e2061abe1aab5297fa25bd52af5caa531a", 11 | "CONTENTFUL_SPACE_ID": "qz0n5cdakyl9", 12 | "CONTENTFUL_DELIVERY_TOKEN": "580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8", 13 | "CONTENTFUL_PREVIEW_TOKEN": "e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b", 14 | "CONTENTFUL_QA_EMPTY_STATES_SPACE_ID": "85si70kq8sjj", 15 | "CONTENTFUL_QA_EMPTY_STATES_DELIVERY_TOKEN": "eecf609f1411e10439f95c7d89a86c2661476bc3f92f8f0ead0e303e8e4ce9f9", 16 | "CONTENTFUL_QA_EMPTY_STATES_PREVIEW_TOKEN": "f9d927308234f773f90be351e72010fa6ca8f0e36b94cb3cc0f84a40c43f2e4d" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_moduleHighlightedCourse.jade: -------------------------------------------------------------------------------- 1 | mixin moduleHighlightedCourse(module) 2 | .module.module-highlighted-course 3 | .module-highlighted-course__wrapper(style='background-image: url(!{module.course.imageUrl});') 4 | .module-highlighted-course__overlay 5 | .module-highlighted-course__content 6 | if(module.course.categories) 7 | .module-highlighted-course__categories 8 | each category in module.course.categories 9 | a.module-highlighted-course__category(href='/courses/categories/!{category.slug}!{base.meta.queryString}') #{category.title} 10 | h2.module-highlighted-course__title 11 | a(href='/courses/!{module.course.slug}!{base.meta.queryString}')= module.course.title 12 | .module-highlighted-course__description-wrapper 13 | p !{module.course.shortDescription} 14 | .module-highlighted-course__link-wrapper 15 | a.module-highlighted-course__link(href='/courses/!{module.course.slug}!{base.meta.queryString}') #{base.labels.viewCourseLabel} 16 | -------------------------------------------------------------------------------- /src/main/resources/templates/landingPage.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_breadcrumb 4 | include mixins/_editorialFeatures 5 | include mixins/_moduleCopy 6 | include mixins/_moduleHeroImage 7 | include mixins/_moduleHighlightedCourse 8 | 9 | block content 10 | .layout-centered 11 | +breadcrumb 12 | .layout-centered 13 | +editorialFeatures(landingPage) 14 | .modules-container 15 | if modules 16 | each module in modules 17 | case module.type 18 | when 'copy' 19 | +moduleCopy(module) 20 | 21 | when 'heroImage' 22 | +moduleHeroImage(module) 23 | 24 | when 'highlightedCourse' 25 | +moduleHighlightedCourse(module) 26 | else 27 | .module.module-highlighted-course 28 | .module-highlighted-course__wrapper 29 | .module-highlighted-course__overlay 30 | .module-highlighted-course__content 31 | h1 32 | | #{base.labels.noContentLabel} 33 | span 34 | | #{base.labels.errorDoesNotExistLabel} 35 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/courses/lessons/modules/CopyModule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.courses.lessons.modules; 2 | 3 | import java.util.Objects; 4 | 5 | public class CopyModule extends Module { 6 | private String copy; 7 | 8 | public CopyModule() { 9 | super("copy"); 10 | } 11 | 12 | public String getCopy() { 13 | return copy; 14 | } 15 | 16 | public CopyModule setCopy(String copy) { 17 | this.copy = copy; 18 | return this; 19 | } 20 | 21 | @Override public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (!(o instanceof CopyModule)) return false; 24 | if (!super.equals(o)) return false; 25 | final CopyModule that = (CopyModule) o; 26 | return Objects.equals(getCopy(), that.getCopy()); 27 | } 28 | 29 | @Override public int hashCode() { 30 | return Objects.hash(super.hashCode(), getCopy()); 31 | } 32 | 33 | @Override public String toString() { 34 | return "CopyModule { " + super.toString() + " " 35 | + "copy = " + getCopy() + " " 36 | + "}"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/base/AnalyticsParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.base; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | import java.util.Objects; 6 | 7 | public class AnalyticsParameter extends MappableType { 8 | private String spaceId; 9 | 10 | public String getSpaceId() { 11 | return spaceId; 12 | } 13 | 14 | public AnalyticsParameter setSpaceId(String spaceId) { 15 | this.spaceId = spaceId; 16 | return this; 17 | } 18 | 19 | @Override public boolean equals(Object o) { 20 | if (this == o) return true; 21 | if (!(o instanceof AnalyticsParameter)) return false; 22 | final AnalyticsParameter that = (AnalyticsParameter) o; 23 | return Objects.equals(getSpaceId(), that.getSpaceId()); 24 | } 25 | 26 | @Override public int hashCode() { 27 | return Objects.hash(getSpaceId()); 28 | } 29 | 30 | @Override public String toString() { 31 | return "AnalyticsParameter { " + super.toString() + " " 32 | + "spaceId = " + getSpaceId() + " " 33 | + "}"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/resources/markdown/complex.md: -------------------------------------------------------------------------------- 1 | A simple Contentful setup consists of a client application reading content, such as this example app, and another application that is writing content, such as the Contentful web app. The client application is reading content by connecting to the Content Delivery API, and the Contentful Web app is writing content by connecting to the Content Mangement API: 2 | 3 | ![minimal contentful setup](//images.contentful.com/ft4tkuv7nwl0/3z7ErmBLIccwQkQkuEY0w4/bd438f4b8c540f56fcc76d75c688baf1/minimal-setup.svg) 4 | 5 | The *Contentful web app* is a single page application that Contentful provides to help with the most common tasks when managing content: 6 | 7 | - Provide an interface for editors to write content. 8 | - Provide an interface for developes to configure a space, model data, define roles and permissions, and set up webhook notifications. 9 | 10 | As mentioned, the Contentful web app is a client that uses the Content Management API. Therefore, you could replicate the functionality that the Contentful web app provides by making an API call. This is a powerful aspect of an API-first design because it helps you to connect Contentful to third-party systems. 11 | 12 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/http/NonInjectedUrlParameterParserTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.http; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Java6Assertions.assertThat; 7 | 8 | public class NonInjectedUrlParameterParserTest { 9 | private UrlParameterParser parser; 10 | 11 | @Before 12 | public void setup() { 13 | parser = new UrlParameterParser(); 14 | } 15 | 16 | @Test 17 | public void addOneParameterToEmptyQueryString() { 18 | final String subject = parser.addToQueryString("", "name", "value"); 19 | 20 | assertThat(subject).isEqualTo("?name=value"); 21 | } 22 | 23 | @Test 24 | public void addTwoParameterToEmptyQueryString() { 25 | final String subject = parser.addToQueryString("?firstName=firstValue", "secondName", "secondValue"); 26 | 27 | assertThat(subject).isEqualTo("?firstName=firstValue&secondName=secondValue"); 28 | } 29 | 30 | @Test 31 | public void updatesUrlValueIfAlreadySet() { 32 | final String subject = parser.addToQueryString("?name=firstValue", "name", "secondValue"); 33 | 34 | assertThat(subject).isEqualTo("?name=secondValue"); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/landing/modules/HighlightedCourseModule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.landing.modules; 2 | 3 | import com.contentful.tea.java.models.courses.Course; 4 | 5 | import java.util.Objects; 6 | 7 | public class HighlightedCourseModule extends BaseModule { 8 | 9 | public HighlightedCourseModule() { 10 | super("highlightedCourse"); 11 | } 12 | 13 | private Course course; 14 | 15 | public Course getCourse() { 16 | return course; 17 | } 18 | 19 | public HighlightedCourseModule setCourse(Course course) { 20 | this.course = course; 21 | return this; 22 | } 23 | 24 | @Override public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (!(o instanceof HighlightedCourseModule)) return false; 27 | final HighlightedCourseModule that = (HighlightedCourseModule) o; 28 | return Objects.equals(getCourse(), that.getCourse()); 29 | } 30 | 31 | @Override public int hashCode() { 32 | 33 | return Objects.hash(getCourse()); 34 | } 35 | 36 | @Override public String toString() { 37 | return "ModuleHighlightedCourse { " + super.toString() + " " 38 | + "course = " + getCourse() + " " 39 | + "}"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/courses/lessons/modules/Module.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.courses.lessons.modules; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | import java.util.Objects; 6 | 7 | public class Module extends MappableType { 8 | private final String type; 9 | 10 | public Module(String type) { 11 | this.type = type; 12 | } 13 | 14 | private String title; 15 | 16 | public String getTitle() { 17 | return title; 18 | } 19 | 20 | public T setTitle(String title) { 21 | this.title = title; 22 | return (T) this; 23 | } 24 | 25 | public String getType() { 26 | return type; 27 | } 28 | 29 | @Override public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (!(o instanceof Module)) return false; 32 | final Module module = (Module) o; 33 | return Objects.equals(getTitle(), module.getTitle()); 34 | } 35 | 36 | @Override public int hashCode() { 37 | return Objects.hash(getTitle()); 38 | } 39 | 40 | @Override public String toString() { 41 | return "Module { " + super.toString() + " " 42 | + "type = " + getType() + ", " 43 | + "title = " + getTitle() + " " 44 | + "}"; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_lesson.jade: -------------------------------------------------------------------------------- 1 | include _editorialFeatures 2 | include _lessonModuleCodeSnippet 3 | include _lessonModuleCopy 4 | include _lessonModuleImage 5 | 6 | mixin lesson(lesson, course, nextLessonSlug) 7 | .lesson 8 | h1.lesson__title #{lesson.title} 9 | +editorialFeatures(lesson) 10 | .lesson__modules 11 | if lesson.modules 12 | each module in lesson.modules 13 | if module.type 14 | case module.type 15 | when 'code' 16 | +lessonModuleCodeSnippet(module) 17 | when 'copy' 18 | +lessonModuleCopy(module) 19 | when 'image' 20 | +lessonModuleImage(module) 21 | else 22 | h2 ️️#{lessonModuleErrorTitle} 23 | p 24 | span #{lessonModuleErrorBody} 25 | strong #{module.sys.id} 26 | else 27 | .lesson-module.lesson-module-copy 28 | .lesson-module-copy__copy 29 | h1 30 | | #{base.labels.noContentLabel} 31 | span 32 | | #{base.labels.errorDoesNotExistLabel} 33 | if nextLessonSlug 34 | a.lesson__cta.cta(href='/courses/!{course.slug}/lessons/!{nextLessonSlug}!{base.meta.queryString}') #{nextLessonLabel} 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/modelconverter/ContentfulModelToMappableTypeConverter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.modelconverter; 2 | 3 | import com.contentful.tea.java.markdown.CommonmarkMarkdownParser; 4 | import com.contentful.tea.java.models.mappable.MappableType; 5 | import com.contentful.tea.java.services.localization.Keys; 6 | import com.contentful.tea.java.services.localization.Localizer; 7 | import com.contentful.tea.java.services.settings.Settings; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | public abstract class ContentfulModelToMappableTypeConverter { 14 | 15 | @Autowired 16 | @SuppressWarnings("unused") 17 | protected Settings settings; 18 | 19 | @Autowired 20 | @SuppressWarnings("unused") 21 | protected Localizer localizer; 22 | 23 | @Autowired 24 | @SuppressWarnings("unused") 25 | protected CommonmarkMarkdownParser markdown; 26 | 27 | public abstract ViewModel convert(ContentfulModel source, int editorialFeaturesDepth); 28 | 29 | protected String t(Keys translateKey) { 30 | return localizer.localize(translateKey); 31 | } 32 | 33 | protected String m(String raw) { 34 | return markdown.parse(raw); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/static/images/icon-python.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/courses.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_breadcrumb 4 | include mixins/_courseCard 5 | include mixins/_editorialFeatures 6 | 7 | block content 8 | .layout-no-sidebar 9 | +breadcrumb 10 | .layout-sidebar 11 | section.layout-sidebar__sidebar 12 | .layout-sidebar__sidebar-header 13 | h2.layout-sidebar__sidebar-title #{base.labels.categoriesLabel} 14 | .layout-sidebar__sidebar-content 15 | .sidebar-menu 16 | ul.sidebar-menu__list 17 | li.sidebar-menu__item 18 | a(href='/courses!{base.meta.queryString}' class='sidebar-menu__link !{base.meta.allCoursesCssClass}') #{base.labels.allCoursesLabel} 19 | each category in categories 20 | li.sidebar-menu__item 21 | a(href='/courses/categories/!{category.slug}!{base.meta.queryString}' class='sidebar-menu__link !{category.cssClass}') #{category.title} 22 | section.layout-sidebar__content 23 | .courses 24 | h1= title 25 | +editorialFeatures(course) 26 | .grid-list 27 | if courses 28 | each course in courses 29 | .grid-list__item 30 | +courseCard(course) 31 | else 32 | .course-card 33 | h2(class='course-card__title') #{base.labels.noContentLabel} 34 | p(class='course-card__description') #{base.labels.errorDoesNotExistLabel} 35 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/landing/LandingPageParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.landing; 2 | 3 | import com.contentful.tea.java.models.base.BaseParameter; 4 | import com.contentful.tea.java.models.landing.modules.BaseModule; 5 | import com.contentful.tea.java.models.mappable.MappableType; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | public class LandingPageParameter extends MappableType { 12 | 13 | private BaseParameter base = new BaseParameter(); 14 | 15 | private List modules = new ArrayList<>(); 16 | 17 | public BaseParameter getBase() { 18 | return base; 19 | } 20 | 21 | public List getModules() { 22 | return modules; 23 | } 24 | 25 | public LandingPageParameter setModules(List modules) { 26 | this.modules = modules; 27 | return this; 28 | } 29 | 30 | public LandingPageParameter addModule(BaseModule... baseModules) { 31 | if (this.modules == null) { 32 | this.modules = new ArrayList<>(); 33 | } 34 | 35 | if (baseModules != null) { 36 | this.modules.addAll(Arrays.asList(baseModules)); 37 | } 38 | 39 | return this; 40 | } 41 | 42 | @Override public String toString() { 43 | return "LandingPageParameter { " + super.toString() + " " 44 | + "base = " + getBase() + ", " 45 | + "modules = " + getModules() + " " 46 | + "}"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/html/JadeHtmlGenerator.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.html; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.io.IOException; 6 | import java.util.Map; 7 | 8 | import de.neuland.jade4j.JadeConfiguration; 9 | import de.neuland.jade4j.template.JadeTemplate; 10 | 11 | import static java.lang.String.format; 12 | import static java.util.Locale.getDefault; 13 | 14 | @Component 15 | @SuppressWarnings("unused") 16 | public class JadeHtmlGenerator { 17 | private static final String BASE_PATH = "src/main/resources/templates"; 18 | private final JadeConfiguration config; 19 | private final String basePath; 20 | 21 | public JadeHtmlGenerator(boolean prettyPrint, String basePath) { 22 | config = new JadeConfiguration(); 23 | config.setPrettyPrint(prettyPrint); 24 | 25 | this.basePath = basePath; 26 | } 27 | 28 | public JadeHtmlGenerator() { 29 | this(false, BASE_PATH); 30 | } 31 | 32 | public String generate(String templateFileName, Map templateMappings) { 33 | final String path = format(getDefault(), "%s/%s", basePath, templateFileName); 34 | try { 35 | final JadeTemplate template = config.getTemplate(path); 36 | return config.renderTemplate(template, templateMappings); 37 | } catch (IOException e) { 38 | throw new IllegalStateException(format("Cannot find template '%s' in '%s'.", templateFileName, path), e); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/modelcreators/ImprintCreator.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.modelcreators; 2 | 3 | import com.contentful.tea.java.markdown.CommonmarkMarkdownParser; 4 | import com.contentful.tea.java.models.imprint.ImprintParameter; 5 | import com.contentful.tea.java.services.localization.Keys; 6 | import com.contentful.tea.java.services.localization.Localizer; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class ImprintCreator { 13 | @Autowired 14 | @SuppressWarnings("unused") 15 | private Localizer localizer; 16 | 17 | @Autowired 18 | @SuppressWarnings("unused") 19 | private CommonmarkMarkdownParser markdown; 20 | 21 | public ImprintParameter create() { 22 | final ImprintParameter imprintParameter = new ImprintParameter(); 23 | imprintParameter.getBase().getMeta().setTitle(t(Keys.imprintLabel)); 24 | 25 | return imprintParameter 26 | .setCompanyLabel(t(Keys.companyLabel)) 27 | .setGermanyLabel(t(Keys.germanyLabel)) 28 | .setManagingDirectorLabel(t(Keys.managingDirectorLabel)) 29 | .setOfficeLabel(t(Keys.officeLabel)) 30 | .setRegistrationCourtLabel(t(Keys.registrationCourtLabel)) 31 | .setVatNumberLabel(t(Keys.vatNumberLabel)) 32 | ; 33 | } 34 | 35 | private String t(Keys translateKey) { 36 | return localizer.localize(translateKey); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/text/InjectTextRule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils.text; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.junit.rules.MethodRule; 5 | import org.junit.runners.model.FrameworkMethod; 6 | import org.junit.runners.model.Statement; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.net.URL; 11 | import java.nio.charset.Charset; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | public class InjectTextRule implements MethodRule { 16 | 17 | @Override public Statement apply(Statement statement, FrameworkMethod method, Object o) { 18 | final InjectText injectText = method.getAnnotation(InjectText.class); 19 | if (injectText != null) { 20 | if (!(o instanceof InjectTextBaseTests)) { 21 | throw new RuntimeException("Test class must extend " 22 | + InjectTextBaseTests.class.getName() 23 | + " when using @" 24 | + InjectText.class.getSimpleName()); 25 | } 26 | final InjectTextBaseTests tests = (InjectTextBaseTests) o; 27 | 28 | final String markdownFilePath = injectText.value(); 29 | try { 30 | URL resource = getClass().getClassLoader().getResource(markdownFilePath); 31 | assertThat(resource).withFailMessage("File not found: " + markdownFilePath).isNotNull(); 32 | tests.injectedText = FileUtils.readFileToString(new File(resource.getFile()), Charset.forName("UTF-8")); 33 | } catch (IOException e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | return statement; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/static/images/icon-nodejs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/HtmlGeneratorTests.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java; 2 | 3 | import com.contentful.tea.java.html.JadeHtmlGenerator; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import java.util.HashMap; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | public class HtmlGeneratorTests { 13 | 14 | private JadeHtmlGenerator generator; 15 | 16 | @Before 17 | public void setup() { 18 | generator = new JadeHtmlGenerator(false, "src/test/resources"); 19 | } 20 | 21 | @Test 22 | public void testSimpleHashMapGeneration() throws Exception { 23 | final HashMap parameter = new HashMap<>(); 24 | parameter.put("title", "my fancy title"); 25 | parameter.put("text", "my fancy text"); 26 | assertThat( 27 | generator.generate("jade/input.jade", parameter) 28 | ).isEqualTo( 29 | "my fancy title

my fancy text

" 30 | ); 31 | } 32 | 33 | @Test 34 | public void testSimpleObjectGeneration() throws Exception { 35 | final Model parameter = new Model("my fancy title", "my fancy text"); 36 | assertThat( 37 | generator.generate("jade/input.jade", parameter) 38 | ).isEqualTo( 39 | "my fancy title

my fancy text

" 40 | ); 41 | } 42 | 43 | private static class Model extends HashMap { 44 | Model(String title, String text) { 45 | put("title", title); 46 | put("text", text); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/courses/Category.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.courses; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | import java.util.Objects; 6 | 7 | public class Category extends MappableType { 8 | private String slug; 9 | private String title; 10 | private String cssClass; 11 | 12 | public String getCssClass() { 13 | return cssClass; 14 | } 15 | 16 | public Category setCssClass(String cssClass) { 17 | this.cssClass = cssClass; 18 | return this; 19 | } 20 | 21 | public String getSlug() { 22 | return slug; 23 | } 24 | 25 | public Category setSlug(String slug) { 26 | this.slug = slug; 27 | return this; 28 | } 29 | 30 | public String getTitle() { 31 | return title; 32 | } 33 | 34 | public Category setTitle(String title) { 35 | this.title = title; 36 | return this; 37 | } 38 | 39 | @Override public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (!(o instanceof Category)) return false; 42 | final Category category = (Category) o; 43 | return Objects.equals(getCssClass(), category.getCssClass()) && 44 | Objects.equals(getSlug(), category.getSlug()) && 45 | Objects.equals(getTitle(), category.getTitle()); 46 | } 47 | 48 | @Override public int hashCode() { 49 | return Objects.hash(getCssClass(), getSlug(), getTitle()); 50 | } 51 | 52 | @Override public String toString() { 53 | return "Category { " + super.toString() + " " 54 | + "cssClass = " + getCssClass() + ", " 55 | + "slug = " + getSlug() + ", " 56 | + "title = " + getTitle() + " " 57 | + "}"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/resources/static/images/icon-java.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/base/Locale.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.base; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | import java.util.Objects; 6 | 7 | public class Locale extends MappableType { 8 | public static final String CSS_CLASS_ACTIVE = "header__controls_button--active"; 9 | 10 | private String code; 11 | private String cssClass; 12 | private String name; 13 | 14 | public String getCode() { 15 | return code; 16 | } 17 | 18 | public Locale setCode(String code) { 19 | this.code = code; 20 | return this; 21 | } 22 | 23 | public String getCssClass() { 24 | return cssClass; 25 | } 26 | 27 | public Locale setCssClass(String cssClass) { 28 | this.cssClass = cssClass; 29 | return this; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public Locale setName(String name) { 37 | this.name = name; 38 | return this; 39 | } 40 | 41 | @Override public boolean equals(Object o) { 42 | if (this == o) return true; 43 | if (!(o instanceof Locale)) return false; 44 | final Locale locale = (Locale) o; 45 | return Objects.equals(getCode(), locale.getCode()) && 46 | Objects.equals(getCssClass(), locale.getCssClass()) && 47 | Objects.equals(getName(), locale.getName()); 48 | } 49 | 50 | @Override public int hashCode() { 51 | return Objects.hash(getCode(), getCssClass(), getName()); 52 | } 53 | 54 | @Override public String toString() { 55 | return "Locale { " + super.toString() + " " 56 | + "code = " + getCode() + ", " 57 | + "cssClass = " + getCssClass() + ", " 58 | + "name = " + getName() + " " 59 | + "}"; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/modelenhancers/EditorialFeaturesEnhancerTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.modelenhancers; 2 | 3 | import com.contentful.java.cda.CDAContentType; 4 | import com.contentful.java.cda.CDAEntry; 5 | import com.contentful.tea.java.MainController; 6 | import com.contentful.tea.java.services.contentful.Contentful; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import static org.assertj.core.api.Java6Assertions.assertThat; 16 | import static org.mockito.BDDMockito.given; 17 | import static org.mockito.Mockito.mock; 18 | 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest(classes = MainController.class) 21 | public class EditorialFeaturesEnhancerTest { 22 | 23 | @Autowired 24 | @SuppressWarnings("unused") 25 | private EditorialFeaturesEnhancer enhancer; 26 | 27 | @MockBean 28 | @SuppressWarnings("unused") 29 | private Contentful contentful; 30 | 31 | @Test 32 | public void createCorrectDeeplink() { 33 | given(contentful.getSpaceId()).willReturn("SPACEID"); 34 | 35 | final CDAEntry entry = mock(CDAEntry.class); 36 | final CDAContentType contentType = mock(CDAContentType.class); 37 | given(contentType.id()).willReturn("CONTENTTYPEID"); 38 | given(entry.contentType()).willReturn(contentType); 39 | given(entry.id()).willReturn("ENTRYID"); 40 | 41 | final String deeplink = enhancer.generateDeeplinkToContentful(entry); 42 | 43 | assertThat(deeplink).isEqualTo("https://app.contentful.com/spaces/SPACEID/entries/ENTRYID?contentTypeId=CONTENTTYPEID"); 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/resources/static/images/icon-swift.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/markdown/CommonmarkMarkdownParser.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.markdown; 2 | 3 | import org.commonmark.node.AbstractVisitor; 4 | import org.commonmark.node.Block; 5 | import org.commonmark.node.Document; 6 | import org.commonmark.node.Node; 7 | import org.commonmark.node.Paragraph; 8 | import org.commonmark.parser.Parser; 9 | import org.commonmark.renderer.html.HtmlRenderer; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * Implementation of {@link MarkdownParser} using the commonsmark markdown parser. 14 | */ 15 | @Component 16 | @SuppressWarnings("unused") 17 | public class CommonmarkMarkdownParser implements MarkdownParser { 18 | private final Parser parser = Parser.builder().build(); 19 | 20 | private final AbstractVisitor simplifyInlineMarkdown = new AbstractVisitor() { 21 | @Override public void visit(Paragraph paragraph) { 22 | super.visit(paragraph); 23 | 24 | final Block parent = paragraph.getParent(); 25 | if (parent instanceof Document) { 26 | if (parent.getFirstChild() == paragraph && parent.getLastChild() == paragraph) { 27 | while (paragraph.getFirstChild() != null) { 28 | paragraph.insertBefore(paragraph.getFirstChild()); 29 | } 30 | paragraph.unlink(); 31 | } 32 | } 33 | } 34 | }; 35 | 36 | private final HtmlRenderer renderer = HtmlRenderer.builder().build(); 37 | 38 | /** 39 | * Parse a given simple text and return html notation of it. 40 | * 41 | * @param toParse the string containing Markdown markup. 42 | * @return a String containing html representation of input, or null on error. 43 | */ 44 | @Override public String parse(String toParse) { 45 | if (toParse == null) { 46 | return null; 47 | } 48 | 49 | final Node node = parser.parse(toParse); 50 | node.accept(simplifyInlineMarkdown); 51 | 52 | return renderer.render(node); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/courses/lessons/modules/ImageModule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.courses.lessons.modules; 2 | 3 | import java.util.Objects; 4 | 5 | public class ImageModule extends Module { 6 | private String imageUrl; 7 | private String caption; 8 | private String missingImageLabel; 9 | 10 | public ImageModule() { 11 | super("image"); 12 | } 13 | 14 | public String getImageUrl() { 15 | return imageUrl; 16 | } 17 | 18 | public ImageModule setImageUrl(String imageUrl) { 19 | this.imageUrl = imageUrl; 20 | return this; 21 | } 22 | 23 | public String getCaption() { 24 | return caption; 25 | } 26 | 27 | public ImageModule setCaption(String caption) { 28 | this.caption = caption; 29 | return this; 30 | } 31 | 32 | public String getMissingImageLabel() { 33 | return missingImageLabel; 34 | } 35 | 36 | public ImageModule setMissingImageLabel(String missingImageLabel) { 37 | this.missingImageLabel = missingImageLabel; 38 | return this; 39 | } 40 | 41 | @Override public boolean equals(Object o) { 42 | if (this == o) return true; 43 | if (!(o instanceof ImageModule)) return false; 44 | if (!super.equals(o)) return false; 45 | final ImageModule that = (ImageModule) o; 46 | return Objects.equals(getImageUrl(), that.getImageUrl()) && 47 | Objects.equals(getCaption(), that.getCaption()) && 48 | Objects.equals(getMissingImageLabel(), that.getMissingImageLabel()); 49 | } 50 | 51 | @Override public int hashCode() { 52 | return Objects.hash(super.hashCode(), getImageUrl(), getCaption(), getMissingImageLabel()); 53 | } 54 | 55 | @Override public String toString() { 56 | return "ImageModule { " + super.toString() + " " 57 | + "caption = " + getCaption() + ", " 58 | + "missingImageLabel = " + getMissingImageLabel() + ", " 59 | + "image = " + getImageUrl() + " " 60 | + "}"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/openjdk:8-node-browsers 6 | steps: 7 | - checkout 8 | - run: 9 | name: The Example App Java - Building and Testing 10 | command: ./gradlew clean test 11 | - run: 12 | name: The Example App Java - Run Server 13 | command: ./gradlew run 14 | background: true 15 | - run: 16 | name: Wait for Started Server to warm up 17 | command: sleep 5 18 | - run: 19 | name: Checkout E2E Tests 20 | command: git clone https://github.com/contentful/the-example-app-e2e-tests.git ./test/e2e 21 | - restore_cache: 22 | keys: 23 | - tea-e2e-tests-{{ checksum "./test/e2e/package.json" }} 24 | - tea-e2e-tests- 25 | - run: 26 | name: Install E2E dependencies 27 | command: sudo apt-get update; sudo apt-get install xvfb libgtk2.0-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 28 | - run: 29 | name: Install E2E Tests 30 | command: cd ./test/e2e && npm install 31 | - save_cache: 32 | key: tea-e2e-tests-{{ checksum "./test/e2e/package.json" }} 33 | paths: 34 | - ./test/e2e/node_modules 35 | - run: 36 | name: Make Cypress different configuration aware 37 | command: mv cypress-circleci.json cypress.json 38 | - run: 39 | name: The Example App Java - E2E 40 | command: ./test/e2e/node_modules/.bin/cypress run 41 | - run: 42 | name: Save test results 43 | command: | 44 | mkdir -p ./junit/ 45 | find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ./junit/ \; 46 | when: always 47 | - store_test_results: 48 | path: ./junit 49 | - store_artifacts: 50 | path: ./junit 51 | - store_artifacts: 52 | path: ./cypress 53 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/base/BaseParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.base; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | import java.util.Objects; 6 | 7 | public class BaseParameter extends MappableType { 8 | private ApiParameter api = new ApiParameter(); 9 | private LocalesParameter locales = new LocalesParameter(); 10 | private MetaParameter meta = new MetaParameter(); 11 | private BreadcrumbParameter breadcrumb = new BreadcrumbParameter(); 12 | private LabelsParameter labels = new LabelsParameter(); 13 | 14 | public ApiParameter getApi() { 15 | return api; 16 | } 17 | 18 | public LocalesParameter getLocales() { 19 | return locales; 20 | } 21 | 22 | public MetaParameter getMeta() { 23 | return meta; 24 | } 25 | 26 | public BreadcrumbParameter getBreadcrumb() { 27 | return breadcrumb; 28 | } 29 | 30 | public LabelsParameter getLabels() { 31 | return labels; 32 | } 33 | 34 | @Override public boolean equals(Object o) { 35 | if (this == o) return true; 36 | if (!(o instanceof BaseParameter)) return false; 37 | final BaseParameter that = (BaseParameter) o; 38 | return Objects.equals(getApi(), that.getApi()) && 39 | Objects.equals(getLocales(), that.getLocales()) && 40 | Objects.equals(getBreadcrumb(), that.getBreadcrumb()) && 41 | Objects.equals(getLabels(), that.getLabels()) && 42 | Objects.equals(getMeta(), that.getMeta()); 43 | } 44 | 45 | @Override public int hashCode() { 46 | return Objects.hash(getApi(), getLocales(), getLabels(), getBreadcrumb(), getMeta()); 47 | } 48 | 49 | @Override public String toString() { 50 | return "BaseParameter { " + super.toString() + " " 51 | + "api = " + getApi() + ", " 52 | + "breadcrumb = " + getBreadcrumb() + ", " 53 | + "locales = " + getLocales() + ", " 54 | + "labels = " + getLabels() + ", " 55 | + "meta = " + getMeta() + " " 56 | + "}"; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/localization/Localizer.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.localization; 2 | 3 | import com.contentful.tea.java.services.settings.Settings; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import static java.lang.String.format; 16 | import static java.util.Locale.getDefault; 17 | import static org.apache.commons.io.FileUtils.readFileToString; 18 | 19 | @Component 20 | public class Localizer { 21 | 22 | @Autowired 23 | public Settings settings; 24 | 25 | private final Map> localizations = new HashMap<>(); 26 | 27 | public Localizer() { 28 | final Gson gson = new GsonBuilder().create(); 29 | populateLocalization(gson, "en-US"); 30 | populateLocalization(gson, "de-DE"); 31 | } 32 | 33 | public String localize(Keys key) { 34 | return localize(settings.getLocale(), key); 35 | } 36 | 37 | String localize(String locale, Keys key) { 38 | final Map localized = localizations.get(locale); 39 | if (localized != null) { 40 | return localized.get(key.name()); 41 | } else { 42 | return localizations.get("en-US").get(key.name()); 43 | } 44 | } 45 | 46 | @SuppressWarnings("unchecked") 47 | private void populateLocalization(Gson gson, String locale) { 48 | final String path = format(getDefault(), "src/main/resources/locales/%s.json", locale); 49 | try { 50 | final String json = readFileToString(new File(path)); 51 | final Map fromJson = gson.fromJson(json, Map.class); 52 | 53 | localizations.put(locale, fromJson); 54 | } catch (IOException e) { 55 | throw new IllegalStateException(format("Cannot find locale '%s' in '%s'.", locale, path), e); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/landing/modules/HeroImageModule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.landing.modules; 2 | 3 | import java.util.Objects; 4 | 5 | public class HeroImageModule extends BaseModule { 6 | String headline; 7 | String backgroundImageUrl; 8 | String backgroundImageTitle; 9 | 10 | public HeroImageModule() { 11 | super("heroImage"); 12 | } 13 | 14 | public String getHeadline() { 15 | return headline; 16 | } 17 | 18 | public HeroImageModule setHeadline(String headline) { 19 | this.headline = headline; 20 | return this; 21 | } 22 | 23 | public String getBackgroundImageUrl() { 24 | return backgroundImageUrl; 25 | } 26 | 27 | public HeroImageModule setBackgroundImageUrl(String backgroundImageUrl) { 28 | this.backgroundImageUrl = backgroundImageUrl; 29 | return this; 30 | } 31 | 32 | public String getBackgroundImageTitle() { 33 | return backgroundImageTitle; 34 | } 35 | 36 | public HeroImageModule setBackgroundImageTitle(String backgroundImageTitle) { 37 | this.backgroundImageTitle = backgroundImageTitle; 38 | return this; 39 | } 40 | 41 | @Override public boolean equals(Object o) { 42 | if (this == o) return true; 43 | if (!(o instanceof HeroImageModule)) return false; 44 | final HeroImageModule that = (HeroImageModule) o; 45 | return Objects.equals(getHeadline(), that.getHeadline()) && 46 | Objects.equals(getBackgroundImageUrl(), that.getBackgroundImageUrl()) && 47 | Objects.equals(getBackgroundImageTitle(), that.getBackgroundImageTitle()); 48 | } 49 | 50 | @Override public int hashCode() { 51 | return Objects.hash(getHeadline(), getBackgroundImageUrl(), getBackgroundImageTitle()); 52 | } 53 | 54 | @Override public String toString() { 55 | return "ModuleHeroImage { " + super.toString() + " " 56 | + "backgroundImageTitle = " + getBackgroundImageTitle() + ", " 57 | + "backgroundImageUrl = " + getBackgroundImageUrl() + ", " 58 | + "headline = " + getHeadline() + " " 59 | + "}"; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/http/EnqueueHttpResponseRule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils.http; 2 | 3 | import org.junit.rules.MethodRule; 4 | import org.junit.runners.model.FrameworkMethod; 5 | import org.junit.runners.model.Statement; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | public class EnqueueHttpResponseRule implements MethodRule { 12 | @Override public Statement apply(Statement statement, FrameworkMethod method, Object o) { 13 | EnqueueHttpResponse enqueueHttpResponse = method.getAnnotation(EnqueueHttpResponse.class); 14 | if (enqueueHttpResponse != null) { 15 | if (!(o instanceof EnqueuedHttpResponseTests)) { 16 | throw new RuntimeException("Test class must extend " 17 | + EnqueuedHttpResponseTests.class.getName() 18 | + " when using @" 19 | + EnqueueHttpResponse.class.getSimpleName()); 20 | } 21 | List responses = new ArrayList(); 22 | responses.addAll(enqueueToTestResponse(enqueueHttpResponse)); 23 | ((EnqueuedHttpResponseTests) o).setResponseQueue(responses); 24 | } 25 | return statement; 26 | } 27 | 28 | private Collection enqueueToTestResponse(EnqueueHttpResponse enqueueHttpResponse) { 29 | final List responses 30 | = new ArrayList( 31 | enqueueHttpResponse.complex().length 32 | + enqueueHttpResponse.value().length); 33 | 34 | for (final ComplexHttpResponse response : enqueueHttpResponse.complex()) { 35 | responses.add(new HttpResponse(response.code(), response.fileName(), response.headers())); 36 | } 37 | 38 | for (final String response : enqueueHttpResponse.defaults()) { 39 | responses.add(new HttpResponse(200, response, new String[]{})); 40 | } 41 | 42 | for (final String response : enqueueHttpResponse.value()) { 43 | responses.add(new HttpResponse(200, response, new String[]{})); 44 | } 45 | 46 | return responses; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/base/ApiParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.base; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | import java.util.Objects; 6 | 7 | public class ApiParameter extends MappableType { 8 | private String cdaButtonCSSClass; 9 | private String cpaButtonCSSClass; 10 | private String currentApiId; 11 | 12 | public String getCdaButtonCSSClass() { 13 | return cdaButtonCSSClass; 14 | } 15 | 16 | public ApiParameter setCdaButtonCSSClass(String cdaButtonCSSClass) { 17 | this.cdaButtonCSSClass = cdaButtonCSSClass; 18 | return this; 19 | } 20 | 21 | public String getCpaButtonCSSClass() { 22 | return cpaButtonCSSClass; 23 | } 24 | 25 | public ApiParameter setCpaButtonCSSClass(String cpaButtonCSSClass) { 26 | this.cpaButtonCSSClass = cpaButtonCSSClass; 27 | return this; 28 | } 29 | 30 | public String getCurrentApiId() { 31 | return currentApiId; 32 | } 33 | 34 | public ApiParameter setCurrentApiId(String currentApiId) { 35 | this.currentApiId = currentApiId; 36 | return this; 37 | } 38 | 39 | @Override public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (!(o instanceof ApiParameter)) return false; 42 | final ApiParameter apiParameter = (ApiParameter) o; 43 | return Objects.equals(getCdaButtonCSSClass(), apiParameter.getCdaButtonCSSClass()) && 44 | Objects.equals(getCpaButtonCSSClass(), apiParameter.getCpaButtonCSSClass()) && 45 | Objects.equals(getCurrentApiId(), apiParameter.getCurrentApiId()); 46 | } 47 | 48 | @Override public int hashCode() { 49 | return Objects.hash( 50 | getCdaButtonCSSClass(), 51 | getCpaButtonCSSClass(), 52 | getCurrentApiId()); 53 | } 54 | 55 | @Override public String toString() { 56 | return "API { " 57 | + "cdaButtonCSSClass = " + getCdaButtonCSSClass() + ", " 58 | + "cpaButtonCSSClass = " + getCpaButtonCSSClass() + ", " 59 | + "currentApiId = " + getCurrentApiId() + " " 60 | + "}"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/resources/templates/course.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_breadcrumb 4 | include mixins/_editorialFeatures 5 | include mixins/_lesson 6 | 7 | block content 8 | .layout-no-sidebar 9 | +breadcrumb 10 | .layout-sidebar 11 | section.layout-sidebar__sidebar 12 | .layout-sidebar__sidebar-header 13 | h2.layout-sidebar__sidebar-title #{tableOfContentsLabel} 14 | .layout-sidebar__sidebar-content 15 | .table-of-contents 16 | .table-of-contents__list 17 | .table-of-contents__item 18 | a(href='/courses/!{course.slug}!{base.meta.queryString}' class='table-of-contents__link !{courseOverviewCssClass}') !{courseOverviewLabel} 19 | each lesson in course.lessons 20 | if lesson 21 | .table-of-contents__item 22 | a(href='/courses/!{course.slug}/lessons/!{lesson.slug}!{base.meta.queryString}' class='table-of-contents__link !{lesson.cssClass}') !{lesson.title} 23 | section.layout-sidebar__content 24 | if course.currentLesson 25 | +lesson(course.currentLesson, course, course.nextLessonSlug) 26 | else 27 | .course 28 | h1.course__title= course.title 29 | +editorialFeatures(course) 30 | .course__overview 31 | h3.course__overview-title #{overviewLabel} 32 | if course.duration 33 | .course__overview-item 34 | svg.course__overview-icon 35 | use(xlink:href='/icons/icons.svg#duration') 36 | .course__overview-value #{durationLabel}: #{course.duration} #{minutesLabel} 37 | if course.skillLevel 38 | .course__overview-item 39 | svg.course__overview-icon 40 | use(xlink:href='/icons/icons.svg#skill-level') 41 | .course__overview-value #{skillLevelLabel}: #{course.skillLevel} 42 | .course__overview-cta-wrapper 43 | if course.lessons && course.lessons[0] 44 | a.course__overview-cta.cta(href='/courses/!{course.slug}/lessons/!{course.lessons[0].slug}!{base.meta.queryString}') #{startCourseLabel} 45 | .course__description !{course.description} 46 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/settings/SettingsTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.settings; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Java6Assertions.assertThat; 6 | 7 | public class SettingsTest { 8 | 9 | @Test 10 | public void canResetSettings() { 11 | final Settings freshSettings = new Settings(); 12 | 13 | final Settings subject = new Settings() 14 | .setPath("SomePath") 15 | .setQueryString("SomeQueryString") 16 | .setLocale("SomeLocale") 17 | .setBaseUrl("SomeBaseUrl") 18 | .setEditorialFeaturesEnabled(true); 19 | 20 | assertThat(freshSettings.toString()).isEqualTo(subject.reset().toString()); 21 | } 22 | 23 | @Test 24 | public void canSaveSettings() { 25 | final Settings subject = new Settings() 26 | .setPath("SomePath") 27 | .setQueryString("SomeQueryString") 28 | .setLocale("SomeLocale") 29 | .setBaseUrl("SomeBaseUrl") 30 | .setEditorialFeaturesEnabled(true); 31 | 32 | final Settings save = subject.save(); 33 | subject.reset(); 34 | 35 | assertThat(save.toString()).isNotEqualTo(subject.toString()); 36 | assertThat(save.getPath()).isEqualTo("SomePath"); 37 | assertThat(save.getQueryString()).isEqualTo("SomeQueryString"); 38 | assertThat(save.getLocale()).isEqualTo("SomeLocale"); 39 | assertThat(save.getBaseUrl()).isEqualTo("SomeBaseUrl"); 40 | assertThat(save.areEditorialFeaturesEnabled()).isTrue(); 41 | } 42 | 43 | @Test 44 | public void canLoadSettings() { 45 | final Settings subject = new Settings() 46 | .setPath("SomePath") 47 | .setQueryString("SomeQueryString") 48 | .setLocale("SomeLocale") 49 | .setBaseUrl("SomeBaseUrl") 50 | .setEditorialFeaturesEnabled(true); 51 | 52 | final Settings load = new Settings().load(subject); 53 | 54 | assertThat(load.getPath()).isEqualTo("SomePath"); 55 | assertThat(load.getQueryString()).isEqualTo("SomeQueryString"); 56 | assertThat(load.getLocale()).isEqualTo("SomeLocale"); 57 | assertThat(load.getBaseUrl()).isEqualTo("SomeBaseUrl"); 58 | assertThat(load.areEditorialFeaturesEnabled()).isTrue(); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/test/resources/models/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "fields": { 5 | "slug": "application-development", 6 | "title": "Application Development" 7 | }, 8 | "sys": { 9 | "contentType": { 10 | "sys": { 11 | "id": "category", 12 | "linkType": "ContentType", 13 | "type": "Link" 14 | } 15 | }, 16 | "createdAt": "2017-12-11T12:13:56.738Z", 17 | "id": "7JhDodrNmwmwGmQqiACW4", 18 | "locale": "en-US", 19 | "revision": 1, 20 | "space": { 21 | "sys": { 22 | "id": "jnzexv31feqf", 23 | "linkType": "Space", 24 | "type": "Link" 25 | } 26 | }, 27 | "type": "Entry", 28 | "updatedAt": "2017-12-11T12:13:56.738Z" 29 | } 30 | }, 31 | { 32 | "fields": { 33 | "slug": "getting-started", 34 | "title": "Getting started" 35 | }, 36 | "sys": { 37 | "contentType": { 38 | "sys": { 39 | "id": "category", 40 | "linkType": "ContentType", 41 | "type": "Link" 42 | } 43 | }, 44 | "createdAt": "2017-12-11T12:14:08.115Z", 45 | "id": "6ucY5w3oswEU6EYSCEi0C8", 46 | "locale": "en-US", 47 | "revision": 1, 48 | "space": { 49 | "sys": { 50 | "id": "jnzexv31feqf", 51 | "linkType": "Space", 52 | "type": "Link" 53 | } 54 | }, 55 | "type": "Entry", 56 | "updatedAt": "2017-12-11T12:14:08.115Z" 57 | } 58 | } 59 | ], 60 | "limit": 100, 61 | "skip": 0, 62 | "sys": { 63 | "type": "Array" 64 | }, 65 | "total": 2 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/modelconverter/ExceptionToErrorParameterTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.modelconverter; 2 | 3 | import com.contentful.tea.java.MainController; 4 | import com.contentful.tea.java.models.errors.ErrorParameter; 5 | import com.contentful.tea.java.services.contentful.Contentful; 6 | import com.contentful.tea.java.services.localization.Keys; 7 | import com.contentful.tea.java.services.localization.Localizer; 8 | 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | 17 | import java.io.IOException; 18 | 19 | import static org.assertj.core.api.Java6Assertions.assertThat; 20 | 21 | @RunWith(SpringRunner.class) 22 | @SpringBootTest(classes = MainController.class) 23 | public class ExceptionToErrorParameterTest { 24 | 25 | @Autowired 26 | @SuppressWarnings("unused") 27 | private ExceptionToErrorParameter converter; 28 | 29 | @Autowired 30 | @SuppressWarnings("unused") 31 | private Localizer localizer; 32 | 33 | @Autowired 34 | @SuppressWarnings("unused") 35 | private Contentful contentful; 36 | 37 | @Before 38 | public void setup() { 39 | contentful.loadFromPreferences(); 40 | } 41 | 42 | @After 43 | public void tearDown() { 44 | contentful.reset(); 45 | } 46 | 47 | @Test 48 | public void convertIoExceptionTo500ErrorParameter() { 49 | final ErrorParameter subject = converter.convert(new IOException("Could successfully convert exception")); 50 | 51 | verifyStaticErrorLabels(subject); 52 | 53 | assertThat(subject.getStack()).contains("Could successfully convert exception"); 54 | assertThat(subject.getStatus()).isEqualTo(404); 55 | } 56 | 57 | private void verifyStaticErrorLabels(ErrorParameter subject) { 58 | assertThat(subject.getBase()).isNotNull(); 59 | assertThat(subject.getSomethingWentWrongLabel()).isEqualTo(t(Keys.somethingWentWrongLabel)); 60 | } 61 | 62 | private String t(Keys errorLabel) { 63 | return localizer.localize(errorLabel); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/base/LocalesParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.base; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | public class LocalesParameter extends MappableType { 11 | private String currentLocaleCode; 12 | private String currentLocaleName; 13 | private List locales = new ArrayList<>(); 14 | 15 | public String getCurrentLocaleCode() { 16 | return currentLocaleCode; 17 | } 18 | 19 | public LocalesParameter setCurrentLocaleCode(String currentLocaleCode) { 20 | this.currentLocaleCode = currentLocaleCode; 21 | return this; 22 | } 23 | 24 | public String getCurrentLocaleName() { 25 | return currentLocaleName; 26 | } 27 | 28 | public LocalesParameter setCurrentLocaleName(String currentLocaleName) { 29 | this.currentLocaleName = currentLocaleName; 30 | return this; 31 | } 32 | 33 | public List getLocales() { 34 | return locales; 35 | } 36 | 37 | public LocalesParameter setLocales(List locales) { 38 | this.locales = locales; 39 | return this; 40 | } 41 | 42 | public LocalesParameter addLocale(Locale... locales) { 43 | this.locales.addAll(Arrays.asList(locales)); 44 | return this; 45 | } 46 | 47 | @Override public boolean equals(Object o) { 48 | if (this == o) return true; 49 | if (!(o instanceof LocalesParameter)) return false; 50 | final LocalesParameter localesParameter1 = (LocalesParameter) o; 51 | return Objects.equals(getCurrentLocaleCode(), localesParameter1.getCurrentLocaleCode()) && 52 | Objects.equals(getCurrentLocaleName(), localesParameter1.getCurrentLocaleName()) && 53 | Objects.equals(getLocales(), localesParameter1.getLocales()); 54 | } 55 | 56 | @Override public int hashCode() { 57 | return Objects.hash( 58 | getCurrentLocaleCode(), 59 | getCurrentLocaleName(), 60 | getLocales()); 61 | } 62 | 63 | @Override public String toString() { 64 | return "Locales { " 65 | + "currentLocaleCode = " + getCurrentLocaleCode() + ", " 66 | + "currentLocaleName = " + getCurrentLocaleName() + ", " 67 | + "locales = " + getLocales() + " " 68 | + "}"; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/resources/templates/mixins/_lessonModuleCodeSnippet.jade: -------------------------------------------------------------------------------- 1 | mixin lessonModuleCodeSnippet(module) 2 | .lesson-module.lesson-module-code 3 | .lesson-module-code__header 4 | if module.curl 5 | a.lesson-module-code__trigger(data-target='curl') cURL 6 | if module.dotNet 7 | a.lesson-module-code__trigger(data-target='dotnet') .NET 8 | if module.javascript 9 | a.lesson-module-code__trigger(data-target='javascript') JavaScript 10 | if module.java 11 | a.lesson-module-code__trigger.lesson-module-code__trigger--active(data-target='java') Java 12 | if module.javaAndroid 13 | a.lesson-module-code__trigger(data-target='android') Android 14 | if module.php 15 | a.lesson-module-code__trigger(data-target='php') PHP 16 | if module.python 17 | a.lesson-module-code__trigger(data-target='python') Python 18 | if module.ruby 19 | a.lesson-module-code__trigger(data-target='ruby') Ruby 20 | if module.swift 21 | a.lesson-module-code__trigger(data-target='swift') Swift 22 | 23 | .lesson-module-code__code-area 24 | if module.curl 25 | .lesson-module-code__code(id='curl') 26 | pre 27 | code.shell= module.curl 28 | if module.dotNet 29 | .lesson-module-code__code(id='dotnet') 30 | pre 31 | code.csharp= module.dotNet 32 | if module.javascript 33 | .lesson-module-code__code(id='javascript') 34 | pre 35 | code.javascript= module.javascript 36 | if module.java 37 | .lesson-module-code__code.lesson-module-code__code--active(id='java') 38 | pre 39 | code.java= module.java 40 | if module.javaAndroid 41 | .lesson-module-code__code(id='android') 42 | pre 43 | code.java= module.javaAndroid 44 | if module.php 45 | .lesson-module-code__code(id='php') 46 | pre 47 | code.php= module.php 48 | if module.python 49 | .lesson-module-code__code(id='python') 50 | pre 51 | code.python= module.python 52 | if module.ruby 53 | .lesson-module-code__code(id='ruby') 54 | pre 55 | code.ruby= module.ruby 56 | if module.swift 57 | .lesson-module-code__code(id='swift') 58 | pre 59 | code.swift= module.swift 60 | 61 | -------------------------------------------------------------------------------- /src/main/resources/static/images/icon-php.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/courses/CoursesParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.courses; 2 | 3 | import com.contentful.tea.java.models.base.BaseParameter; 4 | import com.contentful.tea.java.models.mappable.MappableType; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Objects; 10 | 11 | public class CoursesParameter extends MappableType { 12 | private BaseParameter base = new BaseParameter(); 13 | 14 | private List categories = new ArrayList<>(); 15 | private List courses = new ArrayList<>(); 16 | 17 | private String title; 18 | 19 | public BaseParameter getBase() { 20 | return base; 21 | } 22 | 23 | public List getCourses() { 24 | return courses; 25 | } 26 | 27 | public CoursesParameter setCourses(List courses) { 28 | this.courses = courses; 29 | return this; 30 | } 31 | 32 | public CoursesParameter addCourse(Course... courses) { 33 | this.courses.addAll(Arrays.asList(courses)); 34 | return this; 35 | } 36 | 37 | public List getCategories() { 38 | return categories; 39 | } 40 | 41 | public CoursesParameter setCategories(List categories) { 42 | this.categories = categories; 43 | return this; 44 | } 45 | 46 | public CoursesParameter addCategory(Category... categories) { 47 | this.categories.addAll(Arrays.asList(categories)); 48 | return this; 49 | } 50 | 51 | public String getTitle() { 52 | return title; 53 | } 54 | 55 | public CoursesParameter setTitle(String title) { 56 | this.title = title; 57 | return this; 58 | } 59 | 60 | @Override public boolean equals(Object o) { 61 | if (this == o) return true; 62 | if (!(o instanceof CoursesParameter)) return false; 63 | final CoursesParameter that = (CoursesParameter) o; 64 | return Objects.equals(getBase(), that.getBase()) && 65 | Objects.equals(getCourses(), that.getCourses()) && 66 | Objects.equals(getTitle(), that.getTitle()) && 67 | Objects.equals(getCategories(), that.getCategories()); 68 | } 69 | 70 | @Override public int hashCode() { 71 | return Objects.hash(getBase(), getCourses(), getCategories(), getTitle()); 72 | } 73 | 74 | @Override public String toString() { 75 | return "CoursesParameter { " + super.toString() + " " 76 | + "base = " + getBase() + ", " 77 | + "courses = " + getCourses() + ", " 78 | + "categories = " + getCategories() + " " 79 | + "title = " + getTitle() + " " 80 | + "}"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/courses/lessons/LessonParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.courses.lessons; 2 | 3 | import com.contentful.tea.java.models.base.BaseParameter; 4 | import com.contentful.tea.java.models.courses.lessons.modules.Module; 5 | import com.contentful.tea.java.models.mappable.MappableType; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Objects; 11 | 12 | public class LessonParameter extends MappableType { 13 | 14 | public BaseParameter base = new BaseParameter(); 15 | 16 | private String slug; 17 | private String title; 18 | private List modules = new ArrayList<>(); 19 | private String cssClass; 20 | 21 | public BaseParameter getBase() { 22 | return base; 23 | } 24 | 25 | public String getSlug() { 26 | return slug; 27 | } 28 | 29 | public LessonParameter setSlug(String slug) { 30 | this.slug = slug; 31 | return this; 32 | } 33 | 34 | public String getCssClass() { 35 | return cssClass; 36 | } 37 | 38 | public LessonParameter setCssClass(String cssClass) { 39 | this.cssClass = cssClass; 40 | return this; 41 | } 42 | 43 | public String getTitle() { 44 | return title; 45 | } 46 | 47 | public LessonParameter setTitle(String title) { 48 | this.title = title; 49 | return this; 50 | } 51 | 52 | public List getModules() { 53 | return modules; 54 | } 55 | 56 | public LessonParameter addModule(Module... modules) { 57 | this.modules.addAll(Arrays.asList(modules)); 58 | return this; 59 | } 60 | 61 | public LessonParameter setModules(List modules) { 62 | this.modules = modules; 63 | return this; 64 | } 65 | 66 | @Override public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (!(o instanceof LessonParameter)) return false; 69 | final LessonParameter lesson = (LessonParameter) o; 70 | return Objects.equals(getSlug(), lesson.getSlug()) && 71 | Objects.equals(getTitle(), lesson.getTitle()) && 72 | Objects.equals(getModules(), lesson.getModules()) && 73 | Objects.equals(getCssClass(), lesson.getCssClass()); 74 | } 75 | 76 | @Override public int hashCode() { 77 | 78 | return Objects.hash(getSlug(), getTitle(), getModules(), getCssClass()); 79 | } 80 | 81 | @Override public String toString() { 82 | return "Lesson { " + super.toString() + " " 83 | + "cssClass = " + getCssClass() + ", " 84 | + "modules = " + getModules() + ", " 85 | + "slug = " + getSlug() + ", " 86 | + "title = " + getTitle() + " " 87 | + "}"; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/base/BreadcrumbParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.base; 2 | 3 | import com.contentful.tea.java.models.mappable.MappableType; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | public class BreadcrumbParameter extends MappableType { 11 | 12 | public static class Breadcrumb { 13 | private String url; 14 | private String label; 15 | 16 | public String getUrl() { 17 | return url; 18 | } 19 | 20 | public Breadcrumb setUrl(String url) { 21 | this.url = url; 22 | return this; 23 | } 24 | 25 | public String getLabel() { 26 | return label; 27 | } 28 | 29 | public Breadcrumb setLabel(String label) { 30 | this.label = label; 31 | return this; 32 | } 33 | 34 | @Override public boolean equals(Object o) { 35 | if (this == o) return true; 36 | if (!(o instanceof Breadcrumb)) return false; 37 | final Breadcrumb that = (Breadcrumb) o; 38 | return Objects.equals(getUrl(), that.getUrl()) && 39 | Objects.equals(getLabel(), that.getLabel()); 40 | } 41 | 42 | @Override public int hashCode() { 43 | return Objects.hash(getUrl(), getLabel()); 44 | } 45 | 46 | 47 | /** 48 | * @return a human readable string, representing the object. 49 | */ 50 | @Override public String toString() { 51 | return "Breadcrumb { " 52 | + "label = " + getLabel() + ", " 53 | + "url = " + getUrl() + " " 54 | + "}"; 55 | } 56 | } 57 | 58 | private List breadcrumbs = new ArrayList<>(); 59 | 60 | public List getBreadcrumbs() { 61 | return breadcrumbs; 62 | } 63 | 64 | public BreadcrumbParameter addBreadcrumb(Breadcrumb... breadcrumbs) { 65 | this.breadcrumbs.addAll(Arrays.asList(breadcrumbs)); 66 | return this; 67 | } 68 | 69 | public BreadcrumbParameter setBreadcrumbs(List breadcrumbs) { 70 | this.breadcrumbs = breadcrumbs; 71 | return this; 72 | } 73 | 74 | @Override public boolean equals(Object o) { 75 | if (this == o) return true; 76 | if (!(o instanceof BreadcrumbParameter)) return false; 77 | final BreadcrumbParameter that = (BreadcrumbParameter) o; 78 | return Objects.equals(getBreadcrumbs(), that.getBreadcrumbs()); 79 | } 80 | 81 | @Override public int hashCode() { 82 | return Objects.hash(getBreadcrumbs()); 83 | } 84 | 85 | @Override public String toString() { 86 | return "BreadcrumbParameter { " + super.toString() + " " 87 | + "breadcrumbs = " + getBreadcrumbs() + " " 88 | + "}"; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/resources/static/images/icon-android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/mappable/MappableType.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.mappable; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.LinkedHashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import static com.contentful.tea.java.models.mappable.utilities.Reflection.findMappableSuperFields; 11 | 12 | public class MappableType { 13 | 14 | public Map toMap() { 15 | return toMap(null); 16 | } 17 | 18 | public Map toMap(NullHandler nullHandler) { 19 | final Map map = new LinkedHashMap<>(); 20 | final List fields = new ArrayList<>(); 21 | fields.addAll(Arrays.asList(this.getClass().getDeclaredFields())); 22 | findMappableSuperFields(this.getClass().getSuperclass(), fields); 23 | 24 | for (final Field field : fields) { 25 | if (!field.isSynthetic() && !isAllUpperCase(field)) { 26 | field.setAccessible(true); 27 | Object value = null; 28 | try { 29 | value = field.get(this); 30 | } catch (IllegalAccessException e) { 31 | // could not access own field. 32 | } 33 | 34 | if (value instanceof MappableType) { 35 | value = ((MappableType) value).toMap(nullHandler); 36 | } else if (value instanceof MappableType[]) { 37 | value = mapArray((MappableType[]) value, nullHandler); 38 | } else if (value instanceof Iterable) { 39 | value = mapIterable((Iterable) value, nullHandler); 40 | } else if (value == null && nullHandler != null) { 41 | value = nullHandler.handleNull(field); 42 | } 43 | 44 | if (value != null) { 45 | map.put(field.getName(), value); 46 | } 47 | } 48 | } 49 | 50 | return map; 51 | } 52 | 53 | private boolean isAllUpperCase(Field field) { 54 | return field.getName().toUpperCase().equals(field.getName()); 55 | } 56 | 57 | private Map[] mapArray(MappableType[] mappableArray, NullHandler nullHandler) { 58 | final Map[] array = new Map[mappableArray.length]; 59 | int index = 0; 60 | for (final MappableType element : mappableArray) { 61 | array[index++] = element.toMap(nullHandler); 62 | } 63 | return array; 64 | } 65 | 66 | private List mapIterable(Iterable iterable, NullHandler nullHandler) { 67 | final List list = new ArrayList<>(); 68 | for (final Object object : iterable) { 69 | if (object instanceof MappableType) { 70 | final MappableType mappable = (MappableType) object; 71 | list.add(mappable.toMap(nullHandler)); 72 | } else { 73 | list.add(object); 74 | } 75 | } 76 | return list; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/landing/modules/CopyModule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.landing.modules; 2 | 3 | import com.contentful.tea.java.models.mappable.NullHandler; 4 | 5 | import java.util.Map; 6 | import java.util.Objects; 7 | 8 | public class CopyModule extends BaseModule { 9 | private boolean emphasizeStyle; 10 | private String headline; 11 | private String copy; 12 | private String ctaTitle; 13 | private String ctaLink; 14 | 15 | public CopyModule() { 16 | super("copy"); 17 | } 18 | 19 | public CopyModule setEmphasizeStyle(boolean emphasizeStyle) { 20 | this.emphasizeStyle = emphasizeStyle; 21 | return this; 22 | } 23 | 24 | public boolean hasEmphasizeStyle() { 25 | return emphasizeStyle; 26 | } 27 | 28 | public String getHeadline() { 29 | return headline; 30 | } 31 | 32 | public CopyModule setHeadline(String headline) { 33 | this.headline = headline; 34 | return this; 35 | } 36 | 37 | public String getCopy() { 38 | return copy; 39 | } 40 | 41 | public CopyModule setCopy(String copy) { 42 | this.copy = copy; 43 | return this; 44 | } 45 | 46 | public String getCtaTitle() { 47 | return ctaTitle; 48 | } 49 | 50 | public CopyModule setCtaTitle(String ctaTitle) { 51 | this.ctaTitle = ctaTitle; 52 | return this; 53 | } 54 | 55 | public String getCtaLink() { 56 | return ctaLink; 57 | } 58 | 59 | public CopyModule setCtaLink(String ctaLink) { 60 | this.ctaLink = ctaLink; 61 | return this; 62 | } 63 | 64 | @Override public boolean equals(Object o) { 65 | if (this == o) return true; 66 | if (!(o instanceof CopyModule)) return false; 67 | final CopyModule that = (CopyModule) o; 68 | return Objects.equals(emphasizeStyle, that.emphasizeStyle) && 69 | Objects.equals(getHeadline(), that.getHeadline()) && 70 | Objects.equals(getCopy(), that.getCopy()) && 71 | Objects.equals(getCtaTitle(), that.getCtaTitle()) && 72 | Objects.equals(getCtaLink(), that.getCtaLink()); 73 | } 74 | 75 | @Override public int hashCode() { 76 | return Objects.hash(hasEmphasizeStyle(), getHeadline(), getCopy(), getCtaTitle(), getCtaLink()); 77 | } 78 | 79 | @Override public String toString() { 80 | return "ModuleCopy { " + super.toString() + " " 81 | + "copy = " + getCopy() + ", " 82 | + "ctaLink = " + getCtaLink() + ", " 83 | + "ctaTitle = " + getCtaTitle() + ", " 84 | + "headline = " + getHeadline() + ", " 85 | + "hasEmphasizeStyle = " + hasEmphasizeStyle() + " " 86 | + "}"; 87 | } 88 | 89 | @Override public Map toMap(NullHandler nullHandler) { 90 | final Map map = super.toMap(nullHandler); 91 | map.put("style", hasEmphasizeStyle() ? "--emphasized" : ""); 92 | return map; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/contentful/ContentfulTests.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.contentful; 2 | 3 | import com.contentful.java.cda.CDAClient; 4 | import com.contentful.tea.java.utils.http.EnqueuedHttpResponseTests; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import static com.contentful.tea.java.services.contentful.Contentful.API_CDA; 10 | import static org.assertj.core.api.Java6Assertions.assertThat; 11 | 12 | public class ContentfulTests extends EnqueuedHttpResponseTests { 13 | 14 | private Contentful contentful; 15 | 16 | @Before 17 | public void setup() { 18 | contentful = new Contentful() 19 | .setSpaceId("") 20 | .setPreviewAccessToken("") 21 | .setDeliveryAccessToken("") 22 | ; 23 | } 24 | 25 | @Test 26 | public void differentApiReturnDifferentClients() { 27 | assertThat(contentful.contentfulPreviewClient).isNull(); 28 | 29 | final CDAClient client = contentful.getCurrentClient(); 30 | assertThat(client).isNotNull(); 31 | 32 | contentful.setApi(API_CDA); 33 | assertThat(contentful.getCurrentClient()).isEqualTo(client); 34 | 35 | contentful.setApi(Contentful.API_CPA); 36 | assertThat(contentful.getCurrentClient()).isNotEqualTo(client); 37 | 38 | contentful.setApi(API_CDA); 39 | assertThat(contentful.getCurrentClient()).isEqualTo(client); 40 | } 41 | 42 | @Test 43 | public void canLoad() { 44 | final Contentful subject = new Contentful().load(contentful); 45 | 46 | assertThat(subject.toString()).isEqualTo(contentful.toString()); 47 | } 48 | 49 | @Test 50 | public void canLoadDefaults() { 51 | final Contentful subject = new Contentful().loadFromPreferences(); 52 | 53 | assertThat(subject.getApi()).isEqualTo(API_CDA); 54 | assertThat(subject.getSpaceId()).isEqualTo("qz0n5cdakyl9"); 55 | assertThat(subject.getDeliveryAccessToken()).isEqualTo("580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8"); 56 | assertThat(subject.getPreviewAccessToken()).isEqualTo("e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b"); 57 | } 58 | 59 | @Test 60 | public void canSave() { 61 | final Contentful subject = contentful.save(); 62 | 63 | assertThat(subject.getSpaceId()).isEqualTo(""); 64 | assertThat(subject.getPreviewAccessToken()).isEqualTo(""); 65 | assertThat(subject.getDeliveryAccessToken()).isEqualTo(""); 66 | assertThat(subject.getApi()).isEqualTo(API_CDA); 67 | } 68 | 69 | @Test 70 | public void canReset() { 71 | contentful.reset(); 72 | 73 | assertThat(contentful.getApi()).isEqualTo(API_CDA); 74 | assertThat(contentful.getSpaceId()).isNull(); 75 | assertThat(contentful.getPreviewAccessToken()).isNull(); 76 | assertThat(contentful.getDeliveryAccessToken()).isNull(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/http/EnqueuedHttpResponseTests.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils.http; 2 | 3 | import com.contentful.java.cda.CDAClient; 4 | 5 | import org.apache.commons.io.FileUtils; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Rule; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.net.URL; 13 | import java.nio.charset.Charset; 14 | import java.util.List; 15 | import java.util.logging.LogManager; 16 | 17 | import okhttp3.mockwebserver.MockResponse; 18 | import okhttp3.mockwebserver.MockWebServer; 19 | 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | 22 | public class EnqueuedHttpResponseTests { 23 | private static final String DEFAULT_TOKEN = "test_token"; 24 | private static final String DEFAULT_SPACE = "test_space"; 25 | 26 | protected CDAClient client; 27 | protected MockWebServer server; 28 | protected List responseQueue; 29 | 30 | @Rule public EnqueueHttpResponseRule enqueueResponse = new EnqueueHttpResponseRule(); 31 | 32 | @Before public void setUp() throws Exception { 33 | LogManager.getLogManager().reset(); 34 | server = createServer(); 35 | server.start(); 36 | 37 | client = createClient(); 38 | 39 | if (responseQueue != null) { 40 | for (HttpResponse response : responseQueue) { 41 | enqueue(response); 42 | } 43 | } 44 | } 45 | 46 | @After public void tearDown() throws Exception { 47 | server.shutdown(); 48 | } 49 | 50 | protected CDAClient createClient() { 51 | return createBuilder() 52 | .build(); 53 | } 54 | 55 | protected CDAClient createPreviewClient() { 56 | return createBuilder() 57 | .preview() 58 | .setEndpoint(serverUrl()) 59 | .build(); 60 | } 61 | 62 | protected CDAClient.Builder createBuilder() { 63 | return CDAClient.builder() 64 | .setSpace(DEFAULT_SPACE) 65 | .setToken(DEFAULT_TOKEN) 66 | .setEndpoint(serverUrl()); 67 | } 68 | 69 | protected String serverUrl() { 70 | return "http://" + server.getHostName() + ":" + server.getPort(); 71 | } 72 | 73 | protected MockWebServer createServer() { 74 | return new MockWebServer(); 75 | } 76 | 77 | protected void enqueue(HttpResponse response) throws IOException { 78 | URL resource = getClass().getClassLoader().getResource(response.getFileName()); 79 | assertThat(resource).withFailMessage("File not found: " + response.getFileName()).isNotNull(); 80 | server.enqueue(new MockResponse().setResponseCode(response.getCode()) 81 | .setBody(FileUtils.readFileToString(new File(resource.getFile()), Charset.defaultCharset())) 82 | .setHeaders(response.headers())); 83 | } 84 | 85 | public EnqueuedHttpResponseTests setResponseQueue(List responseQueue) { 86 | this.responseQueue = responseQueue; 87 | return this; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/http/UrlParameterParserTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.http; 2 | 3 | import com.contentful.tea.java.MainController; 4 | import com.contentful.tea.java.services.contentful.Contentful; 5 | import com.contentful.tea.java.services.settings.Settings; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.boot.test.mock.mockito.MockBean; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | import static org.assertj.core.api.Java6Assertions.assertThat; 15 | import static org.mockito.BDDMockito.given; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest(classes = MainController.class) 19 | public class UrlParameterParserTest { 20 | @MockBean 21 | @SuppressWarnings("unused") 22 | private Settings settings; 23 | 24 | @MockBean 25 | @SuppressWarnings("unused") 26 | private Contentful contentful; 27 | 28 | @Autowired 29 | @SuppressWarnings("unused") 30 | private UrlParameterParser parser; 31 | 32 | @Test 33 | public void klingonLocaleGetsSavedAsUrlParameter() { 34 | given(settings.getLocale()).willReturn("tlh"); 35 | 36 | final String subject = parser.appToUrlParameter(); 37 | 38 | assertThat(subject).isEqualTo("?locale=tlh"); 39 | } 40 | 41 | @Test 42 | public void twoParametersGetSavedAsUrl() { 43 | given(settings.getLocale()).willReturn("tlh"); 44 | given(contentful.getApi()).willReturn("rest"); 45 | 46 | final String subject = parser.appToUrlParameter(); 47 | 48 | assertThat(subject).isEqualTo("?api=rest&locale=tlh"); 49 | } 50 | 51 | @Test 52 | public void allParametersGetSavedAndNotMore() { 53 | given(settings.getLocale()).willReturn("tlh"); 54 | given(settings.areEditorialFeaturesEnabled()).willReturn(true); 55 | given(contentful.getApi()).willReturn("rest"); 56 | given(contentful.getDeliveryAccessToken()).willReturn("delivery"); 57 | given(contentful.getPreviewAccessToken()).willReturn("preview"); 58 | given(contentful.getSpaceId()).willReturn("space"); 59 | 60 | final String subject = parser.appToUrlParameter(); 61 | 62 | assertThat(subject).isEqualTo("?preview_token=preview&delivery_token=delivery&editorial_features=enabled&api=rest&locale=tlh&space_id=space"); 63 | } 64 | 65 | @Test 66 | public void editorialFeaturesCanGetDisabled() { 67 | given(settings.areEditorialFeaturesEnabled()).willReturn(false); 68 | 69 | final String subject = parser.appToUrlParameter(); 70 | 71 | assertThat(subject).isEqualTo(""); 72 | } 73 | 74 | @Test 75 | public void editorialFeaturesCanGetEnabled() { 76 | given(settings.areEditorialFeaturesEnabled()).willReturn(true); 77 | 78 | final String subject = parser.appToUrlParameter(); 79 | 80 | assertThat(subject).isEqualTo("?editorial_features=enabled"); 81 | } 82 | } -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/MarkdownManipulatorTests.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java; 2 | 3 | import com.contentful.tea.java.markdown.MarkdownParser; 4 | import com.contentful.tea.java.utils.text.InjectText; 5 | import com.contentful.tea.java.utils.text.InjectTextBaseTests; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest(classes = MainController.class) 17 | public class MarkdownManipulatorTests extends InjectTextBaseTests { 18 | 19 | @Autowired 20 | @SuppressWarnings("unused") 21 | private MarkdownParser markdownParser; 22 | 23 | @Test 24 | public void superSimpleTest() { 25 | assertThat(markdownParser.parse("foobar")).isEqualTo("foobar"); 26 | assertThat(markdownParser.parse("_foobar_")).isEqualTo("foobar"); 27 | assertThat(markdownParser.parse("a\n\n\nb")).isEqualTo("

a

\n

b

\n"); 28 | } 29 | 30 | @Test 31 | @InjectText("markdown/simple.md") 32 | public void testSimpleMarkdown() { 33 | assertThat( 34 | markdownParser.parse(injectedText) 35 | ).isEqualTo( 36 | "Switch to the language from English to German by going to the menu item Locale: U.S. English and selecting German." 37 | ); 38 | } 39 | 40 | @Test 41 | @InjectText("markdown/complex.md") 42 | public void testComplexMarkdown() { 43 | assertThat( 44 | markdownParser.parse(injectedText) 45 | ).isEqualTo( 46 | "

A simple Contentful setup consists of a client application reading content, such as this example app, and another application that is writing content, such as the Contentful web app. The client application is reading content by connecting to the Content Delivery API, and the Contentful Web app is writing content by connecting to the Content Mangement API:

\n" + 47 | "

\"minimal

\n" + 48 | "

The Contentful web app is a single page application that Contentful provides to help with the most common tasks when managing content:

\n" + 49 | "
    \n" + 50 | "
  • Provide an interface for editors to write content.
  • \n" + 51 | "
  • Provide an interface for developes to configure a space, model data, define roles and permissions, and set up webhook notifications.
  • \n" + 52 | "
\n" + 53 | "

As mentioned, the Contentful web app is a client that uses the Content Management API. Therefore, you could replicate the functionality that the Contentful web app provides by making an API call. This is a powerful aspect of an API-first design because it helps you to connect Contentful to third-party systems.

\n" 54 | ); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java; 2 | 3 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import java.io.IOException; 8 | import java.util.Objects; 9 | import java.util.stream.Collectors; 10 | 11 | import javax.servlet.Filter; 12 | import javax.servlet.FilterChain; 13 | import javax.servlet.FilterConfig; 14 | import javax.servlet.ServletException; 15 | import javax.servlet.ServletRequest; 16 | import javax.servlet.ServletResponse; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | 20 | import static java.lang.String.format; 21 | import static java.lang.String.join; 22 | 23 | @Configuration 24 | @EnableConfigurationProperties 25 | @SuppressWarnings("unused") 26 | public class SecurityConfig { 27 | static class HttpsEnforcer implements Filter { 28 | 29 | private FilterConfig filterConfig; 30 | 31 | public static final String X_FORWARDED_PROTO = "x-forwarded-proto"; 32 | 33 | @Override 34 | public void init(FilterConfig filterConfig) throws ServletException { 35 | this.filterConfig = filterConfig; 36 | } 37 | 38 | @Override 39 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 40 | HttpServletRequest request = (HttpServletRequest) servletRequest; 41 | HttpServletResponse response = (HttpServletResponse) servletResponse; 42 | 43 | final String forwardedHeader = request.getHeader(X_FORWARDED_PROTO); 44 | 45 | if ("localhost".equals(request.getServerName())) { 46 | // ignore filters 47 | } else if (forwardedHeader != null && forwardedHeader.indexOf("https") != 0) { 48 | redirectToHttps(request, response); 49 | } else if (!request.isSecure()) { 50 | redirectToHttps(request, response); 51 | } 52 | 53 | if (!response.isCommitted()) { 54 | filterChain.doFilter(request, response); 55 | } 56 | } 57 | 58 | private void redirectToHttps(HttpServletRequest request, HttpServletResponse response) throws IOException { 59 | final String parameter = requestParameterToString(request); 60 | final String redirectUrl = format("https://%s/%s", request.getServerName(), parameter == null ? "" : "?" + parameter); 61 | 62 | response.sendRedirect(redirectUrl); 63 | } 64 | 65 | private String requestParameterToString(HttpServletRequest request) { 66 | return join("&", 67 | request 68 | .getParameterMap() 69 | .entrySet() 70 | .stream() 71 | .map( 72 | e -> e.getValue() != null && e.getValue().length > 0 73 | ? e.getKey() + "=" + e.getValue()[0] 74 | : null 75 | ) 76 | .filter(Objects::nonNull) 77 | .collect(Collectors.toList())); 78 | } 79 | 80 | @Override 81 | public void destroy() { 82 | // nothing 83 | } 84 | } 85 | 86 | @Bean 87 | public Filter httpsEnforcerFilter() { 88 | return new HttpsEnforcer(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/localization/Keys.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.localization; 2 | 3 | public enum Keys { 4 | defaultTitle, 5 | whatIsThisApp, 6 | metaDescription, 7 | metaTwitterCard, 8 | metaImageAlt, 9 | metaImageDescription, 10 | viewOnGithub, 11 | apiSwitcherHelp, 12 | contentDeliveryApiLabel, 13 | contentDeliveryApiHelp, 14 | contentPreviewApiLabel, 15 | contentPreviewApiHelp, 16 | locale, 17 | localeQuestion, 18 | settingsLabel, 19 | logoAlt, 20 | homeLabel, 21 | coursesLabel, 22 | lessonsLabel, 23 | footerDisclaimer, 24 | imprintLabel, 25 | contactUsLabel, 26 | modalTitle, 27 | modalTitleDotnet, 28 | modalTitleRuby, 29 | modalTitlePhp, 30 | modalTitlePython, 31 | modalTitleSwift, 32 | modalTitleAndroid, 33 | modalTitleJava, 34 | modalIntro, 35 | modalIntroDotnet, 36 | modalIntroRuby, 37 | modalIntroPhp, 38 | modalIntroPython, 39 | modalIntroSwift, 40 | modalIntroAndroid, 41 | modalIntroJava, 42 | modalSpaceIntro, 43 | modalPlatforms, 44 | modalSpaceLinkLabel, 45 | modalCTALabel, 46 | editorialFeaturesHint, 47 | draftLabel, 48 | pendingChangesLabel, 49 | lessonModuleErrorTitle, 50 | lessonModuleErrorBody, 51 | nextLessonLabel, 52 | imageErrorTitle, 53 | viewCourseLabel, 54 | categoriesWelcomeLabel, 55 | sitemapWelcomeLabel, 56 | tableOfContentsLabel, 57 | courseOverviewLabel, 58 | overviewLabel, 59 | durationLabel, 60 | minutesLabel, 61 | skillLevelLabel, 62 | startCourseLabel, 63 | categoriesLabel, 64 | allCoursesLabel, 65 | companyLabel, 66 | officeLabel, 67 | germanyLabel, 68 | registrationCourtLabel, 69 | managingDirectorLabel, 70 | vatNumberLabel, 71 | settingsIntroLabel, 72 | changesSavedLabel, 73 | errorOccurredTitleLabel, 74 | errorOccurredMessageLabel, 75 | connectedToSpaceLabel, 76 | spaceIdLabel, 77 | spaceIdHelpText, 78 | accessTokenLabel, 79 | cdaAccessTokenLabel, 80 | cpaAccessTokenLabel, 81 | contentDeliveryApiHelpText, 82 | contentPreviewApiHelpText, 83 | enableEditorialFeaturesLabel, 84 | enableEditorialFeaturesHelpText, 85 | saveSettingsButtonLabel, 86 | fieldIsRequiredLabel, 87 | deliveryKeyInvalidLabel, 88 | spaceOrTokenInvalid, 89 | previewKeyInvalidLabel, 90 | beginnerLabel, 91 | intermediateLabel, 92 | advancedLabel, 93 | editInTheWebAppLabel, 94 | currentLocaleLabel, 95 | hostedLabel, 96 | comingSoonLabel, 97 | credentialSourceLabel, 98 | readMoreLabel, 99 | credentialsLocationLabel, 100 | overwriteCredentialsLabel, 101 | copyLinkLabel, 102 | resetCredentialsLabel, 103 | resetAboveLabel, 104 | closeLabel, 105 | overrideConfigLabel, 106 | loadedFromLocalFileLabel, 107 | usingServerCredentialsLabel, 108 | usingSessionCredentialsLabel, 109 | applicationCredentialsLabel, 110 | noContentLabel, 111 | errorHighlightedCourse, 112 | somethingWentWrongLabel, 113 | errorMessage404Route, 114 | errorMessage404Lesson, 115 | errorMessage404Course, 116 | errorMessage404Category, 117 | hintsLabel, 118 | notFoundErrorHint, 119 | contentModelChangedErrorHint, 120 | draftOrPublishedErrorHint, 121 | localeContentErrorHint, 122 | verifyCredentialsErrorHint, 123 | stackTraceErrorHint, 124 | errorLabel, 125 | stackTraceLabel 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/http/SessionParser.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.http; 2 | 3 | import com.contentful.tea.java.services.contentful.Contentful; 4 | import com.contentful.tea.java.services.settings.Settings; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Enumeration; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import javax.servlet.http.HttpSession; 14 | 15 | import static com.contentful.tea.java.services.http.Constants.EDITORIAL_FEATURES_DISABLED; 16 | import static com.contentful.tea.java.services.http.Constants.EDITORIAL_FEATURES_ENABLED; 17 | 18 | @Component 19 | public class SessionParser { 20 | private static final int TIME_48_HOURS = 48 * 60 * 60; 21 | @Autowired 22 | @SuppressWarnings("unused") 23 | private Contentful contentful; 24 | 25 | @Autowired 26 | @SuppressWarnings("unused") 27 | private Settings settings; 28 | 29 | private final Map manipulatorsByNameMap; 30 | 31 | public SessionParser() { 32 | manipulatorsByNameMap = new HashMap<>(); 33 | manipulatorsByNameMap.put(Constants.NAME_SPACE_ID, new Manipulator() { 34 | @Override public String get() { 35 | return contentful.getSpaceId(); 36 | } 37 | 38 | @Override public void set(String value) { 39 | contentful.setSpaceId(value); 40 | } 41 | }); 42 | manipulatorsByNameMap.put(Constants.NAME_DELIVERY_TOKEN, new Manipulator() { 43 | @Override public String get() { 44 | return contentful.getDeliveryAccessToken(); 45 | } 46 | 47 | @Override public void set(String value) { 48 | contentful.setDeliveryAccessToken(value); 49 | } 50 | }); 51 | manipulatorsByNameMap.put(Constants.NAME_PREVIEW_TOKEN, new Manipulator() { 52 | @Override public String get() { 53 | return contentful.getPreviewAccessToken(); 54 | } 55 | 56 | @Override public void set(String value) { 57 | contentful.setPreviewAccessToken(value); 58 | } 59 | }); 60 | manipulatorsByNameMap.put(Constants.NAME_EDITORIAL_FEATURES, new Manipulator() { 61 | @Override public String get() { 62 | return settings.areEditorialFeaturesEnabled() ? EDITORIAL_FEATURES_ENABLED : EDITORIAL_FEATURES_DISABLED; 63 | } 64 | 65 | @Override public void set(String value) { 66 | settings.setEditorialFeaturesEnabled(value != null && value.toLowerCase().equals(EDITORIAL_FEATURES_ENABLED)); 67 | } 68 | }); 69 | } 70 | 71 | public void loadFromSession(HttpSession session) { 72 | final Enumeration names = session.getAttributeNames(); 73 | for (; names.hasMoreElements(); ) { 74 | final String name = names.nextElement(); 75 | final Manipulator manipulator = manipulatorsByNameMap.get(name); 76 | 77 | if (manipulator != null) { 78 | final Object value = session.getAttribute(name); 79 | manipulator.set(value); 80 | } 81 | } 82 | 83 | session.setMaxInactiveInterval(TIME_48_HOURS); 84 | } 85 | 86 | public void saveToSession(HttpSession session) { 87 | manipulatorsByNameMap 88 | .keySet() 89 | .forEach(name -> session.setAttribute(name, manipulatorsByNameMap.get(name).get())); 90 | } 91 | 92 | interface Manipulator { 93 | T get(); 94 | 95 | void set(T value); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/http/SessionParserTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.http; 2 | 3 | import com.contentful.tea.java.MainController; 4 | import com.contentful.tea.java.services.contentful.Contentful; 5 | import com.contentful.tea.java.services.settings.Settings; 6 | 7 | import org.junit.After; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.mock.web.MockHttpSession; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import java.util.Enumeration; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | import javax.servlet.http.HttpSession; 20 | 21 | import static com.contentful.tea.java.services.http.Constants.NAME_DELIVERY_TOKEN; 22 | import static com.contentful.tea.java.services.http.Constants.NAME_EDITORIAL_FEATURES; 23 | import static com.contentful.tea.java.services.http.Constants.NAME_PREVIEW_TOKEN; 24 | import static com.contentful.tea.java.services.http.Constants.NAME_SPACE_ID; 25 | import static org.assertj.core.api.Java6Assertions.assertThat; 26 | 27 | @RunWith(SpringRunner.class) 28 | @SpringBootTest(classes = MainController.class) 29 | public class SessionParserTest { 30 | 31 | @Autowired 32 | @SuppressWarnings("unused") 33 | private Contentful contentful; 34 | 35 | @Autowired 36 | @SuppressWarnings("unused") 37 | private Settings settings; 38 | 39 | @Autowired 40 | @SuppressWarnings("unused") 41 | private SessionParser parser; 42 | 43 | @After 44 | public void teardown() { 45 | contentful.reset(); 46 | settings.reset(); 47 | } 48 | 49 | @Test 50 | public void parserLoadsSession() { 51 | final HttpSession session = new MockHttpSession(); 52 | 53 | session.setAttribute(NAME_SPACE_ID, "spaceId"); 54 | session.setAttribute(NAME_DELIVERY_TOKEN, "cdaToken"); 55 | session.setAttribute(NAME_PREVIEW_TOKEN, "cpaToken"); 56 | 57 | parser.loadFromSession(session); 58 | 59 | assertThat(contentful.getSpaceId()).isEqualTo("spaceId"); 60 | assertThat(contentful.getDeliveryAccessToken()).isEqualTo("cdaToken"); 61 | assertThat(contentful.getPreviewAccessToken()).isEqualTo("cpaToken"); 62 | } 63 | 64 | @Test 65 | public void parserSavesSession() { 66 | contentful 67 | .setSpaceId("spaceId") 68 | .setDeliveryAccessToken("cdaToken") 69 | .setPreviewAccessToken("cpaToken"); 70 | 71 | final HttpSession session = new MockHttpSession(); 72 | parser.saveToSession(session); 73 | 74 | assertThat(session.getAttributeNames()).isNotNull(); 75 | Map attributes = extractAttributesIntoMap(session); 76 | 77 | final String[] names = attributes.keySet().toArray(new String[attributes.size()]); 78 | assertThat(names) 79 | .containsExactlyInAnyOrder(NAME_DELIVERY_TOKEN, NAME_PREVIEW_TOKEN, NAME_SPACE_ID, NAME_EDITORIAL_FEATURES); 80 | 81 | final Object[] values = attributes.values().toArray(); 82 | assertThat(values) 83 | .containsExactlyInAnyOrder("spaceId", "cdaToken", "cpaToken", "disabled"); 84 | } 85 | 86 | private Map extractAttributesIntoMap(HttpSession session) { 87 | final Map map = new HashMap<>(); 88 | 89 | final Enumeration names = session.getAttributeNames(); 90 | for (; names.hasMoreElements(); ) { 91 | final String name = names.nextElement(); 92 | final Object value = session.getAttribute(name); 93 | map.put(name, value); 94 | } 95 | 96 | return map; 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/settings/Settings.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.settings; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.Objects; 6 | 7 | @Component 8 | public class Settings { 9 | 10 | private static final String DEFAULT_LOCALE = "en-US"; 11 | 12 | private String queryString; 13 | private String baseUrl; 14 | private String path; 15 | private String locale = DEFAULT_LOCALE; 16 | 17 | private boolean editorialFeaturesEnabled; 18 | 19 | public Settings() { 20 | reset(); 21 | } 22 | 23 | public String getLocale() { 24 | return locale; 25 | } 26 | 27 | public Settings setLocale(String locale) { 28 | this.locale = locale; 29 | return this; 30 | } 31 | 32 | public String getQueryString() { 33 | return queryString; 34 | } 35 | 36 | public Settings setQueryString(String queryString) { 37 | this.queryString = queryString; 38 | return this; 39 | } 40 | 41 | public String getPath() { 42 | return path; 43 | } 44 | 45 | public Settings setPath(String path) { 46 | this.path = path; 47 | return this; 48 | } 49 | 50 | public boolean areEditorialFeaturesEnabled() { 51 | return editorialFeaturesEnabled; 52 | } 53 | 54 | public Settings setEditorialFeaturesEnabled(boolean editorialFeaturesEnabled) { 55 | this.editorialFeaturesEnabled = editorialFeaturesEnabled; 56 | return this; 57 | } 58 | 59 | public String getBaseUrl() { 60 | return baseUrl; 61 | } 62 | 63 | public Settings setBaseUrl(String baseUrl) { 64 | this.baseUrl = baseUrl; 65 | return this; 66 | } 67 | 68 | @Override public String toString() { 69 | return "Settings { " 70 | + "locale = " + getLocale() + ", " 71 | + "baseUrl = " + getBaseUrl() + ", " 72 | + "path = " + getPath() + ", " 73 | + "queryString = " + getQueryString() + ", " 74 | + "editorialFeaturesEnabled = " + areEditorialFeaturesEnabled() + " " 75 | + "}"; 76 | } 77 | 78 | @Override public boolean equals(Object o) { 79 | if (this == o) return true; 80 | if (!(o instanceof Settings)) return false; 81 | final Settings settings = (Settings) o; 82 | return Objects.equals(getQueryString(), settings.getQueryString()) && 83 | Objects.equals(getLocale(), settings.getLocale()) && 84 | Objects.equals(areEditorialFeaturesEnabled(), settings.areEditorialFeaturesEnabled()); 85 | } 86 | 87 | @Override public int hashCode() { 88 | return Objects.hash(getBaseUrl(), getQueryString(), getPath(), getLocale(), areEditorialFeaturesEnabled()); 89 | } 90 | 91 | public Settings save() { 92 | return 93 | new Settings() 94 | .setEditorialFeaturesEnabled(areEditorialFeaturesEnabled()) 95 | .setBaseUrl(getBaseUrl()) 96 | .setLocale(getLocale()) 97 | .setPath(getPath()) 98 | .setQueryString(getQueryString()) 99 | ; 100 | } 101 | 102 | public Settings load(Settings lastSettings) { 103 | setEditorialFeaturesEnabled(lastSettings.areEditorialFeaturesEnabled()); 104 | setLocale(lastSettings.getLocale()); 105 | setBaseUrl(lastSettings.getBaseUrl()); 106 | setPath(lastSettings.getPath()); 107 | setQueryString(lastSettings.getQueryString()); 108 | return this; 109 | } 110 | 111 | public Settings reset() { 112 | setEditorialFeaturesEnabled(false); 113 | setLocale(DEFAULT_LOCALE); 114 | setBaseUrl(null); 115 | setPath(null); 116 | setQueryString(null); 117 | 118 | return this; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/services/http/UrlParserTest.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.http; 2 | 3 | import com.contentful.tea.java.MainController; 4 | import com.contentful.tea.java.services.contentful.Contentful; 5 | import com.contentful.tea.java.services.settings.Settings; 6 | 7 | import org.junit.After; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | import java.util.Collections; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static com.contentful.tea.java.services.http.Constants.NAME_API; 19 | import static com.contentful.tea.java.services.http.Constants.NAME_DELIVERY_TOKEN; 20 | import static com.contentful.tea.java.services.http.Constants.NAME_EDITORIAL_FEATURES; 21 | import static com.contentful.tea.java.services.http.Constants.NAME_LOCALE; 22 | import static com.contentful.tea.java.services.http.Constants.NAME_PREVIEW_TOKEN; 23 | import static com.contentful.tea.java.services.http.Constants.NAME_SPACE_ID; 24 | import static java.util.Collections.singletonMap; 25 | import static org.assertj.core.api.Java6Assertions.assertThat; 26 | 27 | @RunWith(SpringRunner.class) 28 | @SpringBootTest(classes = MainController.class) 29 | public class UrlParserTest { 30 | 31 | @Autowired 32 | @SuppressWarnings("unused") 33 | private Contentful contentful; 34 | 35 | @Autowired 36 | @SuppressWarnings("unused") 37 | private Settings settings; 38 | 39 | @Autowired 40 | @SuppressWarnings("unused") 41 | private UrlParameterParser parser; 42 | 43 | @After 44 | public void tearDown() { 45 | contentful.reset(); 46 | settings.reset(); 47 | } 48 | 49 | @Test 50 | public void parsingNoParameterDoesNotChangeSettings() { 51 | final String before = contentful.toString(); 52 | 53 | parser.urlParameterToApp(Collections.emptyMap()); 54 | 55 | final String after = contentful.toString(); 56 | assertThat(before).isEqualTo(after); 57 | } 58 | 59 | @Test 60 | public void parsingNullParameterDoesNotChangeSettings() { 61 | final String before = contentful.toString(); 62 | 63 | parser.urlParameterToApp(null); 64 | 65 | final String after = contentful.toString(); 66 | assertThat(before).isEqualTo(after); 67 | } 68 | 69 | @Test 70 | public void tokenInUrlChangesTokenInSettings() { 71 | parser.urlParameterToApp(singletonMap(NAME_DELIVERY_TOKEN, new String[]{"cda_token"})); 72 | 73 | final String after = contentful.getDeliveryAccessToken(); 74 | assertThat(after).isEqualTo("cda_token"); 75 | } 76 | 77 | @Test 78 | public void allParameterCanGetParsed() { 79 | final Map map = new HashMap<>(); 80 | map.put(NAME_API, new String[]{"cpa"}); 81 | map.put(NAME_SPACE_ID, new String[]{"TEST_NAME_SPACE_ID"}); 82 | map.put(NAME_LOCALE, new String[]{"TEST_NAME_LOCALE"}); 83 | map.put(NAME_DELIVERY_TOKEN, new String[]{"TEST_NAME_DELIVERY_TOKEN"}); 84 | map.put(NAME_PREVIEW_TOKEN, new String[]{"TEST_NAME_PREVIEW_TOKEN"}); 85 | map.put(NAME_EDITORIAL_FEATURES, new String[]{"enabled"}); 86 | 87 | parser.urlParameterToApp(map); 88 | 89 | assertThat(contentful.getApi()).isEqualTo("cpa"); 90 | assertThat(contentful.getSpaceId()).isEqualTo("TEST_NAME_SPACE_ID"); 91 | assertThat(contentful.getDeliveryAccessToken()).isEqualTo("TEST_NAME_DELIVERY_TOKEN"); 92 | assertThat(contentful.getPreviewAccessToken()).isEqualTo("TEST_NAME_PREVIEW_TOKEN"); 93 | assertThat(settings.getLocale()).isEqualTo("TEST_NAME_LOCALE"); 94 | assertThat(settings.areEditorialFeaturesEnabled()).isEqualTo(true); 95 | } 96 | } -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/modelconverter/EntryToLesson.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.modelconverter; 2 | 3 | import com.contentful.java.cda.CDAAsset; 4 | import com.contentful.java.cda.CDAEntry; 5 | import com.contentful.tea.java.models.courses.lessons.LessonParameter; 6 | import com.contentful.tea.java.models.courses.lessons.modules.CodeModule; 7 | import com.contentful.tea.java.models.courses.lessons.modules.CopyModule; 8 | import com.contentful.tea.java.models.courses.lessons.modules.ImageModule; 9 | import com.contentful.tea.java.models.courses.lessons.modules.Module; 10 | import com.contentful.tea.java.services.localization.Keys; 11 | import com.contentful.tea.java.services.modelenhancers.EditorialFeaturesEnhancer; 12 | import com.contentful.tea.java.services.settings.Settings; 13 | 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.List; 18 | 19 | import static com.contentful.java.cda.image.ImageOption.https; 20 | 21 | @Component 22 | public class EntryToLesson extends ContentfulModelToMappableTypeConverter { 23 | 24 | @Autowired 25 | @SuppressWarnings("unused") 26 | private Settings settings; 27 | 28 | @Autowired 29 | @SuppressWarnings("unused") 30 | private EditorialFeaturesEnhancer enhancer; 31 | 32 | @Override 33 | public LessonParameter convert(CDAEntry cdaLesson, int editorialFeaturesDepth) { 34 | final LessonParameter result = new LessonParameter() 35 | .setSlug(cdaLesson.getField("slug")) 36 | .setTitle(cdaLesson.getField("title")); 37 | 38 | final List cdaModules = cdaLesson.getField("modules"); 39 | if (cdaModules != null) { 40 | for (final CDAEntry cdaModule : cdaModules) { 41 | final Module module = createModule(cdaModule); 42 | if (module != null) { 43 | result.addModule(module); 44 | } 45 | 46 | if (editorialFeaturesDepth > 0) { 47 | if (enhancer.isPending(cdaModule)) { 48 | result.getBase().getMeta().setPendingChanges(true); 49 | } 50 | if (enhancer.isDraft(cdaModule)) { 51 | result.getBase().getMeta().setDraft(true); 52 | } 53 | } 54 | } 55 | } 56 | 57 | if (editorialFeaturesDepth > 0) { 58 | enhancer.enhance(cdaLesson, result.getBase()); 59 | } 60 | 61 | return result; 62 | } 63 | 64 | private Module createModule(CDAEntry cdaModule) { 65 | final String title = m(cdaModule.getField("title")); 66 | switch (cdaModule.contentType().id()) { 67 | case "lessonCopy": 68 | return new CopyModule() 69 | .setTitle(title) 70 | .setCopy(m(cdaModule.getField("copy"))) 71 | ; 72 | case "lessonImage": 73 | return new ImageModule() 74 | .setTitle(title) 75 | .setCaption(m(cdaModule.getField("caption"))) 76 | .setMissingImageLabel(t(Keys.imageErrorTitle)) 77 | .setImageUrl(((CDAAsset) cdaModule.getField("image")).urlForImageWith(https())) 78 | ; 79 | case "lessonCodeSnippets": 80 | return new CodeModule() 81 | .setTitle(title) 82 | .setCurl(cdaModule.getField("curl")) 83 | .setDotNet(cdaModule.getField("dotNet")) 84 | .setJava(cdaModule.getField("java")) 85 | .setJavaAndroid(cdaModule.getField("javaAndroid")) 86 | .setJavascript(cdaModule.getField("javascript")) 87 | .setPhp(cdaModule.getField("php")) 88 | .setPython(cdaModule.getField("python")) 89 | .setRuby(cdaModule.getField("ruby")) 90 | .setSwift(cdaModule.getField("swift")) 91 | ; 92 | default: 93 | return null; 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/imprint/ImprintParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.imprint; 2 | 3 | import com.contentful.tea.java.models.base.BaseParameter; 4 | import com.contentful.tea.java.models.mappable.MappableType; 5 | 6 | import java.util.Objects; 7 | 8 | public class ImprintParameter extends MappableType { 9 | private BaseParameter base = new BaseParameter(); 10 | 11 | private String companyLabel; 12 | private String officeLabel; 13 | private String germanyLabel; 14 | private String registrationCourtLabel; 15 | private String managingDirectorLabel; 16 | private String vatNumberLabel; 17 | 18 | public BaseParameter getBase() { 19 | return base; 20 | } 21 | 22 | public String getCompanyLabel() { 23 | return companyLabel; 24 | } 25 | 26 | public ImprintParameter setCompanyLabel(String companyLabel) { 27 | this.companyLabel = companyLabel; 28 | return this; 29 | } 30 | 31 | public String getOfficeLabel() { 32 | return officeLabel; 33 | } 34 | 35 | public ImprintParameter setOfficeLabel(String officeLabel) { 36 | this.officeLabel = officeLabel; 37 | return this; 38 | } 39 | 40 | public String getGermanyLabel() { 41 | return germanyLabel; 42 | } 43 | 44 | public ImprintParameter setGermanyLabel(String germanyLabel) { 45 | this.germanyLabel = germanyLabel; 46 | return this; 47 | } 48 | 49 | public String getRegistrationCourtLabel() { 50 | return registrationCourtLabel; 51 | } 52 | 53 | public ImprintParameter setRegistrationCourtLabel(String registrationCourtLabel) { 54 | this.registrationCourtLabel = registrationCourtLabel; 55 | return this; 56 | } 57 | 58 | public String getManagingDirectorLabel() { 59 | return managingDirectorLabel; 60 | } 61 | 62 | public ImprintParameter setManagingDirectorLabel(String managingDirectorLabel) { 63 | this.managingDirectorLabel = managingDirectorLabel; 64 | return this; 65 | } 66 | 67 | public String getVatNumberLabel() { 68 | return vatNumberLabel; 69 | } 70 | 71 | public ImprintParameter setVatNumberLabel(String vatNumberLabel) { 72 | this.vatNumberLabel = vatNumberLabel; 73 | return this; 74 | } 75 | 76 | @Override public boolean equals(Object o) { 77 | if (this == o) return true; 78 | if (!(o instanceof ImprintParameter)) return false; 79 | final ImprintParameter that = (ImprintParameter) o; 80 | return Objects.equals(base, that.base) && 81 | Objects.equals(getCompanyLabel(), that.getCompanyLabel()) && 82 | Objects.equals(getOfficeLabel(), that.getOfficeLabel()) && 83 | Objects.equals(getGermanyLabel(), that.getGermanyLabel()) && 84 | Objects.equals(getRegistrationCourtLabel(), that.getRegistrationCourtLabel()) && 85 | Objects.equals(getManagingDirectorLabel(), that.getManagingDirectorLabel()) && 86 | Objects.equals(getVatNumberLabel(), that.getVatNumberLabel()); 87 | } 88 | 89 | @Override public int hashCode() { 90 | return Objects.hash(base, getCompanyLabel(), getOfficeLabel(), getGermanyLabel(), getRegistrationCourtLabel(), getManagingDirectorLabel(), getVatNumberLabel()); 91 | } 92 | 93 | /** 94 | * @return a human readable string, representing the object. 95 | */ 96 | @Override public String toString() { 97 | return "ImprintParameter { " + super.toString() + " " 98 | + "companyLabel = " + getCompanyLabel() + ", " 99 | + "germanyLabel = " + getGermanyLabel() + ", " 100 | + "managingDirectorLabel = " + getManagingDirectorLabel() + ", " 101 | + "officeLabel = " + getOfficeLabel() + ", " 102 | + "registrationCourtLabel = " + getRegistrationCourtLabel() + ", " 103 | + "vatNumberLabel = " + getVatNumberLabel() + " " 104 | + "}"; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/modelconverter/ExceptionToErrorParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.modelconverter; 2 | 3 | import com.contentful.java.cda.CDAHttpException; 4 | import com.contentful.tea.java.models.base.BaseParameter; 5 | import com.contentful.tea.java.models.base.Locale; 6 | import com.contentful.tea.java.models.errors.ErrorParameter; 7 | import com.contentful.tea.java.models.exceptions.TeaException; 8 | import com.contentful.tea.java.services.StaticContentSetter; 9 | import com.contentful.tea.java.services.contentful.Contentful; 10 | import com.contentful.tea.java.services.localization.Keys; 11 | import com.contentful.tea.java.services.localization.Localizer; 12 | 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.core.convert.converter.Converter; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.Collections; 18 | import java.util.List; 19 | import java.util.Objects; 20 | import java.util.function.Predicate; 21 | 22 | import static java.util.stream.Collectors.toList; 23 | import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; 24 | 25 | @Component 26 | public class ExceptionToErrorParameter implements Converter { 27 | 28 | @Autowired 29 | @SuppressWarnings("unused") 30 | StaticContentSetter staticContentSetter; 31 | 32 | @Autowired 33 | @SuppressWarnings("unused") 34 | Localizer localizer; 35 | 36 | @Autowired 37 | @SuppressWarnings("unused") 38 | Contentful contentful; 39 | 40 | @Override 41 | public ErrorParameter convert(Throwable source) { 42 | final ErrorParameter errorParameter = new ErrorParameter(); 43 | final BaseParameter base = errorParameter.getBase(); 44 | staticContentSetter.applyErrorContent(base); 45 | base.getLocales() 46 | .setCurrentLocaleName("U.S. English") 47 | .setCurrentLocaleCode("en-US") 48 | .addLocale( 49 | new Locale() 50 | .setCode("en-US") 51 | .setName("U.S. English") 52 | .setCssClass(Locale.CSS_CLASS_ACTIVE), 53 | new Locale() 54 | .setCode("de-DE") 55 | .setName("Germany (Germany)") 56 | .setCssClass("") 57 | ) 58 | ; 59 | 60 | base.getMeta().setTitle(t(Keys.errorOccurredTitleLabel)); 61 | 62 | return errorParameter 63 | .setHints(hintKeysToHints(source)) 64 | .setResponseDataLabel(t(Keys.errorLabel)) 65 | .setSomethingWentWrongLabel(t(Keys.somethingWentWrongLabel)) 66 | .setResponseData(getResponseData(source)) 67 | .setStatus(404) 68 | .setTryLabel(t(Keys.hintsLabel)) 69 | .setStackTraceLabel(t(Keys.stackTraceLabel)) 70 | .setStack(getStackTrace(source)) 71 | .setUseCustomCredentials(contentful.isUsingCustomCredentials()) 72 | .setResetCredentialsLabel(t(Keys.resetCredentialsLabel)) 73 | ; 74 | } 75 | 76 | private List hintKeysToHints(Throwable source) { 77 | final List keys = source instanceof TeaException ? ((TeaException) source).createHints() : Collections.singletonList(Keys.notFoundErrorHint); 78 | return keys 79 | .stream() 80 | .filter(Objects::nonNull) 81 | .map(this::t) 82 | .filter(not(String::isEmpty)) 83 | .collect(toList()); 84 | } 85 | 86 | private static Predicate not(Predicate t) { 87 | return t.negate(); 88 | } 89 | 90 | private String getResponseData(Throwable source) { 91 | final Throwable cause = source.getCause(); 92 | return cause instanceof CDAHttpException ? cause.toString() : null; 93 | } 94 | 95 | public String t(Keys key) { 96 | return localizer.localize(key); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/courses/lessons/modules/CodeModule.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.courses.lessons.modules; 2 | 3 | import java.util.Objects; 4 | 5 | public class CodeModule extends Module { 6 | private String curl; 7 | private String dotNet; 8 | private String javascript; 9 | private String java; 10 | private String javaAndroid; 11 | private String php; 12 | private String python; 13 | private String ruby; 14 | private String swift; 15 | 16 | public CodeModule() { 17 | super("code"); 18 | } 19 | 20 | public String getCurl() { 21 | return curl; 22 | } 23 | 24 | public CodeModule setCurl(String curl) { 25 | this.curl = curl; 26 | return this; 27 | } 28 | 29 | public String getDotNet() { 30 | return dotNet; 31 | } 32 | 33 | public CodeModule setDotNet(String dotNet) { 34 | this.dotNet = dotNet; 35 | return this; 36 | } 37 | 38 | public String getJavascript() { 39 | return javascript; 40 | } 41 | 42 | public CodeModule setJavascript(String javascript) { 43 | this.javascript = javascript; 44 | return this; 45 | } 46 | 47 | public String getJava() { 48 | return java; 49 | } 50 | 51 | public CodeModule setJava(String java) { 52 | this.java = java; 53 | return this; 54 | } 55 | 56 | public String getJavaAndroid() { 57 | return javaAndroid; 58 | } 59 | 60 | public CodeModule setJavaAndroid(String javaAndroid) { 61 | this.javaAndroid = javaAndroid; 62 | return this; 63 | } 64 | 65 | public String getPhp() { 66 | return php; 67 | } 68 | 69 | public CodeModule setPhp(String php) { 70 | this.php = php; 71 | return this; 72 | } 73 | 74 | public String getPython() { 75 | return python; 76 | } 77 | 78 | public CodeModule setPython(String python) { 79 | this.python = python; 80 | return this; 81 | } 82 | 83 | public String getRuby() { 84 | return ruby; 85 | } 86 | 87 | public CodeModule setRuby(String ruby) { 88 | this.ruby = ruby; 89 | return this; 90 | } 91 | 92 | public String getSwift() { 93 | return swift; 94 | } 95 | 96 | public CodeModule setSwift(String swift) { 97 | this.swift = swift; 98 | return this; 99 | } 100 | 101 | @Override public boolean equals(Object o) { 102 | if (this == o) return true; 103 | if (!(o instanceof CodeModule)) return false; 104 | if (!super.equals(o)) return false; 105 | final CodeModule that = (CodeModule) o; 106 | return Objects.equals(getCurl(), that.getCurl()) && 107 | Objects.equals(getDotNet(), that.getDotNet()) && 108 | Objects.equals(getJavascript(), that.getJavascript()) && 109 | Objects.equals(getJava(), that.getJava()) && 110 | Objects.equals(getJavaAndroid(), that.getJavaAndroid()) && 111 | Objects.equals(getPhp(), that.getPhp()) && 112 | Objects.equals(getPython(), that.getPython()) && 113 | Objects.equals(getRuby(), that.getRuby()) && 114 | Objects.equals(getSwift(), that.getSwift()); 115 | } 116 | 117 | @Override public int hashCode() { 118 | return Objects.hash(super.hashCode(), getCurl(), getDotNet(), getJavascript(), getJava(), getJavaAndroid(), getPhp(), getPython(), getRuby(), getSwift()); 119 | } 120 | 121 | @Override public String toString() { 122 | return "CodeModule { " + super.toString() + " " 123 | + "curl = " + getCurl() + ", " 124 | + "dotNet = " + getDotNet() + ", " 125 | + "java = " + getJava() + ", " 126 | + "javaAndroid = " + getJavaAndroid() + ", " 127 | + "javascript = " + getJavascript() + ", " 128 | + "php = " + getPhp() + ", " 129 | + "python = " + getPython() + ", " 130 | + "ruby = " + getRuby() + ", " 131 | + "swift = " + getSwift() + " " 132 | + "}"; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/MappableTypeTests.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java; 2 | 3 | import com.contentful.tea.java.models.base.BaseParameter; 4 | import com.contentful.tea.java.models.mappable.MappableType; 5 | 6 | import org.junit.Test; 7 | 8 | import java.util.Arrays; 9 | import java.util.LinkedHashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.assertj.core.api.Assertions.entry; 15 | 16 | public class MappableTypeTests { 17 | @Test 18 | public void simpleMappable() { 19 | Map nestedMap = new LinkedHashMap<>(); 20 | nestedMap.put("x", 12); 21 | nestedMap.put("y", 24.0); 22 | 23 | assertThat( 24 | new MappableType() { 25 | private int i = 42; 26 | private String s = "string"; 27 | private MappableType nested = new MappableType() { 28 | private int x = 12; 29 | private double y = 24.0; 30 | }; 31 | }.toMap() 32 | ) 33 | .containsExactly( 34 | entry("i", 42), 35 | entry("s", "string"), 36 | entry("nested", nestedMap) 37 | ); 38 | } 39 | 40 | @Test 41 | public void mappableWithArrayElementsTest() { 42 | final Map actual = new MappableType() { 43 | private String[] array = new String[]{"a", "b", "c"}; 44 | }.toMap(); 45 | 46 | assertThat(actual).containsOnlyKeys("array"); 47 | final String[] array = (String[]) actual.get("array"); 48 | assertThat(array.length).isEqualTo(3); 49 | assertThat(array[0]).isEqualTo("a"); 50 | assertThat(array[1]).isEqualTo("b"); 51 | assertThat(array[2]).isEqualTo("c"); 52 | } 53 | 54 | @Test 55 | public void mappableWithArrayContainingMappableElementsTest() { 56 | final Map actual = new MappableType() { 57 | private final MappableType[] array = new MappableType[]{ 58 | new MappableType() { 59 | int x = 12; 60 | }, 61 | new MappableType() { 62 | int y = 13; 63 | }, 64 | new MappableType() { 65 | int z = 14; 66 | } 67 | }; 68 | }.toMap(); 69 | 70 | assertThat(actual).containsKey("array"); 71 | 72 | final Object arrayObject = actual.get("array"); 73 | assertThat(arrayObject).isInstanceOf(Map[].class); 74 | 75 | final Map[] array = (Map[]) arrayObject; 76 | assertThat(array[0].get("x")).isEqualTo(12); 77 | assertThat(array[1].get("y")).isEqualTo(13); 78 | assertThat(array[2].get("z")).isEqualTo(14); 79 | } 80 | 81 | @Test 82 | public void mappableWithListContainingMappableElementsTest() { 83 | final Map actual = new MappableType() { 84 | private final List list = Arrays.asList( 85 | new MappableType() { 86 | int x = 12; 87 | }, 88 | new MappableType() { 89 | int y = 13; 90 | }, 91 | new MappableType() { 92 | int z = 14; 93 | } 94 | ); 95 | }.toMap(); 96 | 97 | 98 | assertThat(actual).containsKey("list"); 99 | 100 | final Object listObject = actual.get("list"); 101 | assertThat(listObject).isInstanceOf(List.class); 102 | 103 | final List> list = (List>) listObject; 104 | assertThat(list.get(0).get("x")).isEqualTo(12); 105 | assertThat(list.get(1).get("y")).isEqualTo(13); 106 | assertThat(list.get(2).get("z")).isEqualTo(14); 107 | } 108 | 109 | @Test 110 | public void simpleLayoutParameter() { 111 | final BaseParameter base = new BaseParameter(); 112 | base.getMeta() 113 | .setTitle("foo"); 114 | 115 | final Map map = base.toMap(); 116 | assertThat(map).containsKeys("api", "locales", "meta"); 117 | assertThat(((Map) map.get("meta")).get("title")).isEqualTo("foo"); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **Note**: This repo is no longer officially maintained as of Jan, 2023. 2 | > Feel free to use it, fork it and patch it for your own needs. 3 | 4 | ## The Java example app 5 | 6 | [![CircleCI](https://circleci.com/gh/contentful/the-example-app.java.svg?style=svg)](https://circleci.com/gh/contentful/the-example-app.java) 7 | 8 | The Java example app teaches the very basics of how to work with Contentful: 9 | 10 | - consume content from the Contentful Delivery and Preview APIs 11 | - model content 12 | - edit content through the Contentful web app 13 | 14 | The app demonstrates how decoupling content from its presentation enables greater flexibility and facilitates shipping higher quality software more quickly. 15 | 16 | ![screenshot](https://images.contentful.com/88dyiqcr7go8/1ITbJQboPGmAOcEegiw20y/a9a045dff5be48bb6c09c375d33a2ed5/the-example-app-java.herokuapp.com.png) 17 | 18 | You can see a hosted version of `The Java example app` on Heroku. 19 | 20 | ## What is Contentful? 21 | 22 | [Contentful](https://www.contentful.com) provides a content infrastructure for digital teams to power content in websites, apps, and devices. Unlike a CMS, Contentful was built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship digital products faster. 23 | 24 | ## Requirements 25 | 26 | * Java (JDK 8+) 27 | * Git 28 | * Contentful CLI (only for write access) 29 | 30 | Without any changes, this app is connected to a Contentful space with read-only access. To experience the full end-to-end Contentful experience, you need to connect the app to a Contentful space with read _and_ write access. This enables you to see how content editing in the Contentful web app works and how content changes propagate to this app. 31 | 32 | ## Common setup 33 | 34 | Clone the repo and install the dependencies. 35 | 36 | ```bash 37 | git clone https://github.com/contentful/the-example-app.java.git 38 | ``` 39 | 40 | ## Steps for read-only access 41 | 42 | To start the server, run the following 43 | 44 | ```bash 45 | ./gradlew run 46 | ``` 47 | 48 | Open [http://localhost:8080](http://localhost:8080) and take a look around. 49 | 50 | 51 | ## Steps for read and write access (recommended) 52 | 53 | Step 1: Install the [Contentful CLI](https://www.npmjs.com/package/contentful-cli) 54 | 55 | Step 2: Login to Contentful through the CLI. It will help you to create a [free account](https://www.contentful.com/sign-up/) if you don't have one already. 56 | ``` 57 | contentful login 58 | ``` 59 | Step 3: Create a new space 60 | ``` 61 | contentful space create --name 'My space for the example app' 62 | ``` 63 | Step 4: Seed the new space with the content model. Replace the `SPACE_ID` with the id returned from the create command executed in step 3 64 | ``` 65 | contentful space seed -s '' -t the-example-app 66 | ``` 67 | Step 5: Head to the Contentful web app's API section and grab `SPACE_ID`, `DELIVERY_ACCESS_TOKEN`, `PREVIEW_ACCESS_TOKEN`. 68 | 69 | Step 6: Open [application.properties](/src/main/resources/application.properties) and inject your credentials so it looks like this 70 | 71 | ``` 72 | spaceId=qz0n5cdakyl9 73 | deliveryToken=580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8 74 | previewToken=e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b 75 | version=1.0.0 76 | application=The example Java app 77 | ``` 78 | 79 | Step 7: To start the server, run the following 80 | 81 | ```bash 82 | ./gradlew run 83 | ``` 84 | 85 | Final Step: 86 | 87 | Open [http://localhost:8080?editorial_features=enabled](http://localhost:8080?editorial_features=enabled) and take a look around. This URL flag adds an _Edit_ button in the app on every editable piece of content which will take you back to Contentful web app where you can make changes. It also adds _Draft_ and _Pending Changes_ status indicators to all content if relevant. 88 | 89 | ## Deploy to Heroku 90 | You can also deploy this app to Heroku: 91 | 92 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 93 | 94 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/modelconverter/EntryToLandingPage.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.modelconverter; 2 | 3 | import com.contentful.java.cda.CDAAsset; 4 | import com.contentful.java.cda.CDAEntry; 5 | import com.contentful.tea.java.models.landing.LandingPageParameter; 6 | import com.contentful.tea.java.models.landing.modules.BaseModule; 7 | import com.contentful.tea.java.models.landing.modules.CopyModule; 8 | import com.contentful.tea.java.models.landing.modules.HeroImageModule; 9 | import com.contentful.tea.java.models.landing.modules.HighlightedCourseModule; 10 | import com.contentful.tea.java.services.localization.Keys; 11 | import com.contentful.tea.java.services.modelenhancers.EditorialFeaturesEnhancer; 12 | 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.util.List; 17 | 18 | @Component 19 | public class EntryToLandingPage extends ContentfulModelToMappableTypeConverter { 20 | 21 | @Autowired 22 | @SuppressWarnings("unused") 23 | private EntryToCourse courseConverter; 24 | 25 | @Autowired 26 | @SuppressWarnings("unused") 27 | private EditorialFeaturesEnhancer enhancer; 28 | 29 | @Override 30 | public LandingPageParameter convert(CDAEntry entry, int editorialFeaturesDepth) { 31 | final LandingPageParameter parameter = new LandingPageParameter(); 32 | parameter.getBase().getMeta().setTitle(t(Keys.homeLabel)); 33 | parameter.getBase().getLabels().setErrorDoesNotExistLabel(t(Keys.errorHighlightedCourse)); 34 | 35 | addModules(parameter, entry, editorialFeaturesDepth); 36 | 37 | if (editorialFeaturesDepth > 0) { 38 | enhancer.enhance(entry, parameter.getBase()); 39 | } 40 | return parameter; 41 | } 42 | 43 | private void addModules(LandingPageParameter parameter, CDAEntry entry, int editorialFeaturesDepth) { 44 | final List contentModules = entry.getField("contentModules"); 45 | if (contentModules == null) { 46 | return; 47 | } 48 | 49 | for (final CDAEntry module : contentModules) { 50 | final BaseModule moduleParameter = createNewModuleParameter(module, editorialFeaturesDepth); 51 | 52 | if (moduleParameter != null) { 53 | parameter.addModule(moduleParameter); 54 | } 55 | 56 | if (editorialFeaturesDepth > 0) { 57 | if (enhancer.isDraft(module)) { 58 | parameter.getBase().getMeta().setDraft(true); 59 | } 60 | 61 | if (enhancer.isPending(module)) { 62 | parameter.getBase().getMeta().setPendingChanges(true); 63 | } 64 | } 65 | } 66 | } 67 | 68 | private BaseModule createNewModuleParameter(CDAEntry module, int editorialFeaturesDepth) { 69 | switch (module.contentType().id()) { 70 | case "layoutCopy": 71 | return new CopyModule() 72 | .setHeadline(module.getField("headline")) 73 | .setCopy(module.getField("copy")) 74 | .setCtaLink(module.getField("ctaLink")) 75 | .setCtaTitle(module.getField("ctaTitle")) 76 | .setEmphasizeStyle("Emphasized".equals(module.getField("visualStyle"))) 77 | ; 78 | case "layoutHighlightedCourse": 79 | final CDAEntry course = module.getField("course"); 80 | if (course != null) { 81 | return new HighlightedCourseModule() 82 | .setCourse( 83 | courseConverter.convert( 84 | new EntryToCourse.Compound() 85 | .setCourse(course), 86 | editorialFeaturesDepth - 1 87 | ).getCourse()); 88 | } else { 89 | return null; 90 | } 91 | case "layoutHeroImage": 92 | return new HeroImageModule() 93 | .setHeadline(module.getField("headline")) 94 | .setBackgroundImageTitle(module.getField("backgroundImageTitle")) 95 | .setBackgroundImageUrl(backgroundImageUrlFromModule(module)) 96 | ; 97 | default: 98 | return null; 99 | } 100 | } 101 | 102 | private String backgroundImageUrlFromModule(CDAEntry module) { 103 | final CDAAsset asset = module.getField("backgroundImage"); 104 | return asset.url(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/models/LessonsModelTests.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models; 2 | 3 | import com.contentful.java.cda.CDAEntry; 4 | import com.contentful.tea.java.MainController; 5 | import com.contentful.tea.java.models.courses.lessons.LessonParameter; 6 | import com.contentful.tea.java.models.courses.lessons.modules.CodeModule; 7 | import com.contentful.tea.java.models.courses.lessons.modules.CopyModule; 8 | import com.contentful.tea.java.models.courses.lessons.modules.ImageModule; 9 | import com.contentful.tea.java.services.contentful.Contentful; 10 | import com.contentful.tea.java.services.modelconverter.ArrayToCourses; 11 | import com.contentful.tea.java.services.modelconverter.EntryToLandingPage; 12 | import com.contentful.tea.java.services.modelconverter.EntryToLesson; 13 | import com.contentful.tea.java.services.settings.Settings; 14 | import com.contentful.tea.java.utils.http.EnqueueHttpResponse; 15 | import com.contentful.tea.java.utils.http.EnqueuedHttpResponseTests; 16 | 17 | import org.junit.After; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.boot.test.context.SpringBootTest; 23 | import org.springframework.boot.test.mock.mockito.MockBean; 24 | import org.springframework.test.context.junit4.SpringRunner; 25 | 26 | import static org.assertj.core.api.Java6Assertions.assertThat; 27 | import static org.mockito.BDDMockito.given; 28 | 29 | @RunWith(SpringRunner.class) 30 | @SpringBootTest(classes = MainController.class) 31 | public class LessonsModelTests extends EnqueuedHttpResponseTests { 32 | 33 | @Autowired 34 | @SuppressWarnings("unused") 35 | private EntryToLandingPage landingPageConverter; 36 | 37 | @Autowired 38 | @SuppressWarnings("unused") 39 | private ArrayToCourses coursesConverter; 40 | 41 | @Autowired 42 | @SuppressWarnings("unused") 43 | private EntryToLesson lessonConverter; 44 | 45 | @MockBean 46 | @SuppressWarnings("unused") 47 | private Contentful contentful; 48 | 49 | @Autowired 50 | @SuppressWarnings("unused") 51 | private Settings settings; 52 | 53 | @Before 54 | public void setup() { 55 | given(this.contentful.getCurrentClient()).willReturn(client); 56 | } 57 | 58 | @After 59 | public void tearDown() { 60 | contentful.reset(); 61 | settings.reset(); 62 | } 63 | 64 | @Test 65 | @EnqueueHttpResponse({"lessons/complete.json", "defaults/locales.json"}) 66 | public void lessonTest() { 67 | settings.setPath("/courses/one_course"); 68 | settings.setQueryString(""); 69 | 70 | final CDAEntry cdaLesson = client.fetch(CDAEntry.class).one("2SAYsnajosIkCOWqSmKaio"); 71 | 72 | final LessonParameter lesson = lessonConverter.convert(cdaLesson, 2); 73 | assertThat(lesson.getSlug()).isEqualTo("complete_lesson"); 74 | assertThat(lesson.getTitle()).isEqualTo("Complete Lesson > all the modules"); 75 | 76 | assertThat(lesson.getModules()).hasSize(3); 77 | 78 | int i = 0; 79 | assertThat(lesson.getModules().get(i)).isInstanceOf(CopyModule.class); 80 | assertThat(lesson.getModules().get(i).getTitle()).isEqualTo("Complete Lesson > Copy"); 81 | assertThat(((CopyModule) lesson.getModules().get(i)).getCopy()).isEqualTo("This is the copy..."); 82 | i++; 83 | 84 | assertThat(lesson.getModules().get(i)).isInstanceOf(ImageModule.class); 85 | assertThat(lesson.getModules().get(i).getTitle()).isEqualTo("Complete Lesson > Image Module"); 86 | final ImageModule imageModule = (ImageModule) lesson.getModules().get(i); 87 | assertThat(imageModule.getCaption()).isEqualTo("Image Module"); 88 | assertThat(imageModule.getImageUrl()).isEqualTo("https://images.contentful.com/jnzexv31feqf/PKYPMsOlqK4SAwEOwMQky/2ccd7c30325fab8a4f34b35cc4e7e427/foo"); 89 | i++; 90 | 91 | assertThat(lesson.getModules().get(i)).isInstanceOf(CodeModule.class); 92 | assertThat(lesson.getModules().get(i).getTitle()).isEqualTo("Complete Lesson > Code Module"); 93 | final CodeModule codeModule = (CodeModule) lesson.getModules().get(i); 94 | assertThat(codeModule.getCurl()).isEqualTo("curl"); 95 | assertThat(codeModule.getJava()).isEqualTo("java"); 96 | assertThat(codeModule.getJavaAndroid()).isEqualTo("java-android"); 97 | assertThat(codeModule.getJavascript()).isEqualTo("javascript"); 98 | assertThat(codeModule.getPhp()).isEqualTo("php"); 99 | assertThat(codeModule.getRuby()).isEqualTo("ruby"); 100 | assertThat(codeModule.getPython()).isEqualTo("python"); 101 | i++; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/resources/templates/settings.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_breadcrumb 4 | 5 | mixin renderError (error) 6 | if error.message 7 | .form-item__error-wrapper 8 | svg.form-item__error-icon 9 | use(xlink:href='/icons/icons.svg#error') 10 | .form-item__error-message= error.message 11 | 12 | block content 13 | .layout-centered-small 14 | +breadcrumb 15 | h1= title 16 | p #{settingsIntroLabel} 17 | 18 | if successful 19 | .status-block.status-block--success 20 | svg.status-block__icon.status-block__icon--success 21 | use(xlink:href='/icons/icons.svg#success') 22 | .status-block__content 23 | .status-block__title #{changesSavedLabel} 24 | 25 | if errors.hasErrors 26 | .status-block.status-block--error 27 | svg.status-block__icon.status-block__icon--error 28 | use(xlink:href='/icons/icons.svg#error') 29 | .status-block__content 30 | .status-block__title #{errorOccurredTitleLabel} 31 | .status-block__message #{errorOccurredMessageLabel} 32 | 33 | if !errors.hasErrors 34 | .status-block.status-block--info 35 | svg.status-block__icon.status-block__icon--info 36 | use(xlink:href='/icons/icons.svg#info') 37 | .status-block__content 38 | .status-block__message 39 | if !usesCustomCredentials 40 | p 41 | em #{usingServerCredentialsLabel} 42 | p 43 | strong #{connectedToSpaceLabel}: 44 | br 45 | span #{spaceName} (#{spaceId}) 46 | p 47 | strong #{credentialSourceLabel}: 48 | br 49 | span #{loadedFromLocalFileLabel}  50 | a(href="#{loadedFromLocalFileUrl}" target="_blank" rel="noopener") #{loadedFromLocalFileName} 51 | p 52 | em #{overrideConfigLabel} 53 | else 54 | p 55 | em #{usingSessionCredentialsLabel} 56 | p 57 | strong #{connectedToSpaceLabel}: 58 | br 59 | span #{spaceName} (#{spaceId}) 60 | 61 | form(action='/settings?reset=true' method='POST') 62 | p 63 | strong #{applicationCredentialsLabel}: 64 | br 65 | button(type="submit") #{resetCredentialsLabel} 66 | br 67 | a(href='#{deepLinkUrl}' class="status-block__sharelink") #{copyLinkLabel} 68 | p 69 | em #{overrideConfigLabel} 70 | 71 | form(action='/settings!{base.meta.queryString}' method="POST" class="form") 72 | .form-item 73 | label(for="input-space-id") #{spaceIdLabel} 74 | input(type="text" name="space_id" id="input-space-id" required value=settings.spaceId) 75 | if errors.spaceId 76 | +renderError(errors.spaceId) 77 | .form-item__help-text #{spaceIdHelpText} 78 | 79 | .form-item 80 | label(for="input-delivery-token") #{deliveryTokenLabel} 81 | input(type="text" name="delivery_token" id="input-delivery-token" required value=settings.deliveryToken) 82 | if errors.deliveryToken 83 | +renderError(errors.deliveryToken) 84 | .form-item__help-text 85 | | #{contentDeliveryApiHelpText}  86 | a(href='https://www.contentful.com/developers/docs/references/content-delivery-api/' target='_blank' rel='noopener') Content Delivery API. 87 | 88 | .form-item 89 | label(for="input-preview-token") #{previewTokenLabel} 90 | input(type="text" name="preview_token" id="input-preview-token" required value=settings.previewToken) 91 | if errors.previewToken 92 | +renderError(errors.previewToken) 93 | .form-item__help-text 94 | | #{contentPreviewApiHelpText}  95 | a(href='https://www.contentful.com/developers/docs/references/content-preview-api/' target='_blank' rel='noopener') Content Preview API. 96 | 97 | .form-item 98 | if editorialFeaturesEnabled 99 | input(type="checkbox" name="editorial_features" id="input-editorial-features" checked value="enabled") 100 | else 101 | input(type="checkbox" name="editorial_features" id="input-editorial-features" value="enabled") 102 | label(for="input-editorial-features") #{enableEditorialFeaturesLabel} 103 | .form-item__help-text #{enableEditorialFeaturesHelpText} 104 | 105 | .form-item 106 | input.cta(type="submit" value=saveSettingsButtonLabel) 107 | -------------------------------------------------------------------------------- /src/main/resources/static/images/contentful-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/services/modelenhancers/EditorialFeaturesEnhancer.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.services.modelenhancers; 2 | 3 | import com.contentful.java.cda.CDAClient; 4 | import com.contentful.java.cda.CDAEntry; 5 | import com.contentful.java.cda.CDAHttpException; 6 | import com.contentful.java.cda.CDAResourceNotFoundException; 7 | import com.contentful.java.cda.LocalizedResource; 8 | import com.contentful.tea.java.models.base.BaseParameter; 9 | import com.contentful.tea.java.services.contentful.Contentful; 10 | import com.contentful.tea.java.services.settings.Settings; 11 | 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.time.LocalDateTime; 16 | import java.time.temporal.ChronoField; 17 | import java.time.temporal.ChronoUnit; 18 | import java.time.temporal.TemporalAccessor; 19 | import java.util.Locale; 20 | 21 | import static com.contentful.tea.java.services.contentful.Contentful.API_CPA; 22 | import static java.lang.String.format; 23 | import static java.time.format.DateTimeFormatter.ISO_DATE_TIME; 24 | 25 | @Component 26 | public class EditorialFeaturesEnhancer { 27 | @Autowired 28 | @SuppressWarnings("unused") 29 | private Contentful contentful; 30 | 31 | @Autowired 32 | @SuppressWarnings("unused") 33 | private Settings settings; 34 | 35 | public void enhance(CDAEntry entry, BaseParameter base) { 36 | final boolean editorialFeatures = settings.areEditorialFeaturesEnabled(); 37 | if (!base.getMeta().hasEditorialFeatures()) { 38 | base.getMeta().setEditorialFeatures(editorialFeatures); 39 | } 40 | 41 | if (editorialFeatures 42 | && contentful.getApi().equals(API_CPA) 43 | && !base.getMeta().hasPendingChanges() 44 | && !base.getMeta().isDraft()) { 45 | updateDraftAndPendingStates(entry, base); 46 | } 47 | 48 | base 49 | .getMeta() 50 | .setDeeplinkToContentful(generateDeeplinkToContentful(entry)); 51 | } 52 | 53 | String generateDeeplinkToContentful(CDAEntry entry) { 54 | final String spaceId = contentful.getSpaceId(); 55 | final String entryId = entry.id(); 56 | final String contentTypeId = entry.contentType().id(); 57 | 58 | return format(Locale.getDefault(), 59 | "https://app.contentful.com/spaces/%s/entries/%s?contentTypeId=%s", 60 | spaceId, entryId, contentTypeId); 61 | } 62 | 63 | private void updateDraftAndPendingStates(CDAEntry currentEntry, BaseParameter base) { 64 | base.getMeta().setDraft(base.getMeta().isDraft() || isDraft(currentEntry)); 65 | base.getMeta().setPendingChanges(base.getMeta().hasPendingChanges() || isPending(currentEntry)); 66 | } 67 | 68 | public boolean isDraft(CDAEntry previewEntry) { 69 | final String api = contentful.getApi(); 70 | try { 71 | final CDAClient client = contentful 72 | .setApi(Contentful.API_CDA) 73 | .getCurrentClient(); 74 | 75 | final CDAEntry publishedEntry = client 76 | .fetch(CDAEntry.class) 77 | .one(previewEntry.id()); 78 | 79 | if (publishedEntry == null) { 80 | return true; 81 | } 82 | } catch (CDAResourceNotFoundException exception) { 83 | return true; 84 | } catch (CDAHttpException exception) { 85 | if (exception.responseCode() == 404) { 86 | return true; 87 | } 88 | } catch (Throwable t) { 89 | return false; 90 | } finally { 91 | contentful.setApi(api); 92 | } 93 | 94 | return false; 95 | } 96 | 97 | public boolean isPending(CDAEntry previewEntry) { 98 | final String api = contentful.getApi(); 99 | try { 100 | final CDAClient client = contentful 101 | .setApi(Contentful.API_CDA) 102 | .getCurrentClient(); 103 | 104 | final CDAEntry publishedEntry = client 105 | .fetch(CDAEntry.class) 106 | .one(previewEntry.id()); 107 | 108 | if (isPreviewUpdatedMoreRecently(publishedEntry, previewEntry)) { 109 | return true; 110 | } 111 | } catch (Throwable t) { 112 | return false; 113 | } finally { 114 | contentful.setApi(api); 115 | } 116 | 117 | return false; 118 | } 119 | 120 | private boolean isPreviewUpdatedMoreRecently(LocalizedResource publishedEntry, LocalizedResource previewEntry) { 121 | final LocalDateTime publishedUpdatedAtTime = getPublishedAtTime(publishedEntry); 122 | final LocalDateTime previewUpdatedAtTime = getPublishedAtTime(previewEntry); 123 | 124 | return previewUpdatedAtTime.compareTo(publishedUpdatedAtTime) > 0; 125 | } 126 | 127 | private LocalDateTime getPublishedAtTime(LocalizedResource entry) { 128 | final CharSequence updatedAt = entry.getAttribute("updatedAt"); 129 | final TemporalAccessor accessor = ISO_DATE_TIME.parse(updatedAt); 130 | final LocalDateTime dateTime = LocalDateTime.from(accessor); 131 | return dateTime.minus(dateTime.get(ChronoField.NANO_OF_SECOND), ChronoUnit.NANOS); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/com/contentful/tea/java/utils/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.utils; 2 | 3 | import com.contentful.tea.java.models.base.AnalyticsParameter; 4 | import com.contentful.tea.java.models.base.BaseParameter; 5 | import com.contentful.tea.java.models.base.Locale; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class TestUtils { 10 | public static void createBaseParameter(BaseParameter base) { 11 | base.getApi() 12 | .setCdaButtonCSSClass("TEST-setCdaButtonCSSClass") 13 | .setCpaButtonCSSClass("TEST-setCpaButtonCSSClass") 14 | .setCurrentApiId("TEST-setCurrentApiId") 15 | ; 16 | 17 | base.getMeta() 18 | .setAllCoursesCssClass("TEST-setAllCoursesCSSClass") 19 | .setCoursesCSSClass("TEST-setCoursesCSSClass") 20 | .setCurrentPath("/TEST-setCurrentPath") 21 | .setHomeCSSClass("TEST-setHomeCSSClass") 22 | .setQueryString("TEST-setQueryString") 23 | .setTitle("TEST-setTitle") 24 | .setUpperMenuCSSClass("TEST-setUpperMenuCSSClass") 25 | .setDeeplinkToContentful("TEST-setDeeplinkToContentful") 26 | .setAllPlatformsQueryString("TEST-allPlatformsQueryString") 27 | .setAnalytics(new AnalyticsParameter().setSpaceId("TEST-spaceID")) 28 | ; 29 | 30 | base.getLocales() 31 | .setCurrentLocaleCode("TEST-setCurrentLocaleCode") 32 | .setCurrentLocaleName("TEST-setCurrentLocaleName") 33 | .addLocale( 34 | new Locale() 35 | .setCode("en-US") 36 | .setName("\uD83C\uDDFA\uD83C\uDDF8") 37 | .setCssClass("inactive") 38 | ) 39 | .addLocale( 40 | new Locale() 41 | .setCode("de-DE") 42 | .setName("\uD83C\uDDE9\uD83C\uDDEA") 43 | .setCssClass("active") 44 | ); 45 | 46 | base 47 | .getLabels() 48 | .setAllCoursesLabel("TEST-setAllCoursesLabel") 49 | .setApiSwitcherHelp("TEST-setApiSwitcherHelp") 50 | .setCategoriesLabel("TEST-setCategoriesLabel") 51 | .setComingSoonLabel("TEST-setComingSoonLabel") 52 | .setContactUsLabel("TEST-setContactUsLabel") 53 | .setContentDeliveryApiHelp("TEST-setContentDeliveryApiHelp") 54 | .setContentDeliveryApiLabel("TEST-setContentDeliveryApiLabel") 55 | .setContentPreviewApiHelp("TEST-setContentPreviewApiHelp") 56 | .setContentPreviewApiLabel("TEST-setContentPreviewApiLabel") 57 | .setCoursesLabel("TEST-setCoursesLabel") 58 | .setCurrentApiLabel("TEST-setCurrentApiLabel") 59 | .setDescription("TEST-setDescription") 60 | .setDraftLabel("TEST-setDraftLabel") 61 | .setEditInWebAppLabel("TEST-setEditInWebAppLabel") 62 | .setEditorialFeaturesHint("TEST-setEditorialFeaturesHint") 63 | .setFooterDisclaimer("TEST-setFooterDisclaimer") 64 | .setHomeLabel("TEST-setHomeLabel") 65 | .setHostedLabel("TEST-setHostedLabel") 66 | .setImageAlt("TEST-setImageAlt") 67 | .setImageDescription("TEST-setImageDescription") 68 | .setImprintLabel("TEST-setImprintLabel") 69 | .setLocaleLabel("TEST-setLocaleLabel") 70 | .setLocaleQuestion("TEST-setLocaleQuestion") 71 | .setLogoAlt("TEST-setLogoAlt") 72 | .setModalCTALabel("TEST-setModalCTALabel") 73 | .setModalIntro("TEST-setModalIntro") 74 | .setModalPlatforms("TEST-setModalPlatforms") 75 | .setModalSpaceIntro("TEST-setModalSpaceIntro") 76 | .setModalSpaceLinkLabel("TEST-setModalSpaceLinkLabel") 77 | .setModalTitle("TEST-setModalTitle") 78 | .setPendingChangesLabel("TEST-setPendingChangesLabel") 79 | .setSettingsLabel("TEST-setSettingsLabel") 80 | .setTwitterCard("TEST-setTwitterCard") 81 | .setViewCourseLabel("TEST-setViewCourseLabel") 82 | .setViewOnGitHub("TEST-setViewOnGitHub") 83 | .setWhatIsThisApp("TEST-setWhatIsThisApp") 84 | ; 85 | } 86 | 87 | public static void assertBaseParameterInHtml(String generatedHtml) { 88 | assertThat(generatedHtml) 89 | .doesNotContain("\uD83D\uDE31") 90 | .doesNotContain("!{") 91 | .doesNotContain("#{") 92 | .contains("") 93 | .contains("\uD83C\uDDE9\uD83C\uDDEA") 94 | .contains("\uD83C\uDDFA\uD83C\uDDF8") 95 | .contains("active") 96 | .contains("de-DE") 97 | .contains("inactive") 98 | .contains("TEST-setCdaButtonCSSClass") 99 | .contains("TEST-setCoursesCSSClass") 100 | .contains("TEST-setCpaButtonCSSClass") 101 | .contains("TEST-setCurrentApiId") 102 | .contains("TEST-setCurrentLocaleCode") 103 | .contains("TEST-setCurrentLocaleName") 104 | .contains("/TEST-setCurrentPath") 105 | .contains("TEST-setHomeCSSClass") 106 | .contains("TEST-setQueryString") 107 | .contains("TEST-setUpperMenuCSSClass") 108 | .contains("TEST-spaceID") 109 | ; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/resources/static/icons/icons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/errors/ErrorParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.errors; 2 | 3 | import com.contentful.tea.java.models.base.BaseParameter; 4 | import com.contentful.tea.java.models.mappable.MappableType; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | public class ErrorParameter extends MappableType { 11 | private BaseParameter base = new BaseParameter(); 12 | 13 | private String responseData; 14 | private String responseDataLabel; 15 | private String stack; 16 | private String somethingWentWrongLabel; 17 | private String stackTraceLabel; 18 | private String tryLabel; 19 | private boolean useCustomCredentials; 20 | private String resetCredentialsLabel; 21 | private int status; 22 | private List hints = new ArrayList<>(); 23 | 24 | public BaseParameter getBase() { 25 | return base; 26 | } 27 | 28 | public String getResponseData() { 29 | return responseData; 30 | } 31 | 32 | public ErrorParameter setResponseData(String responseData) { 33 | this.responseData = responseData; 34 | return this; 35 | } 36 | 37 | public String getResponseDataLabel() { 38 | return responseDataLabel; 39 | } 40 | 41 | public ErrorParameter setResponseDataLabel(String responseDataLabel) { 42 | this.responseDataLabel = responseDataLabel; 43 | return this; 44 | } 45 | 46 | public String getStack() { 47 | return stack; 48 | } 49 | 50 | public ErrorParameter setStack(String stack) { 51 | this.stack = stack; 52 | return this; 53 | } 54 | 55 | public String getSomethingWentWrongLabel() { 56 | return somethingWentWrongLabel; 57 | } 58 | 59 | public ErrorParameter setSomethingWentWrongLabel(String somethingWentWrongLabel) { 60 | this.somethingWentWrongLabel = somethingWentWrongLabel; 61 | return this; 62 | } 63 | 64 | public String getStackTraceLabel() { 65 | return stackTraceLabel; 66 | } 67 | 68 | public ErrorParameter setStackTraceLabel(String stackTraceLabel) { 69 | this.stackTraceLabel = stackTraceLabel; 70 | return this; 71 | } 72 | 73 | public int getStatus() { 74 | return status; 75 | } 76 | 77 | public ErrorParameter setStatus(int status) { 78 | this.status = status; 79 | return this; 80 | } 81 | 82 | public String getTryLabel() { 83 | return tryLabel; 84 | } 85 | 86 | public ErrorParameter setTryLabel(String tryLabel) { 87 | this.tryLabel = tryLabel; 88 | return this; 89 | } 90 | 91 | public List getHints() { 92 | return hints; 93 | } 94 | 95 | public ErrorParameter setHints(List hints) { 96 | this.hints.clear(); 97 | this.hints.addAll(hints); 98 | 99 | return this; 100 | } 101 | 102 | public ErrorParameter addHint(String hint) { 103 | this.hints.add(hint); 104 | return this; 105 | } 106 | 107 | public boolean usesCustomCredentials() { 108 | return useCustomCredentials; 109 | } 110 | 111 | public ErrorParameter setUseCustomCredentials(boolean useCustomCredentials) { 112 | this.useCustomCredentials = useCustomCredentials; 113 | return this; 114 | } 115 | 116 | public String getResetCredentialsLabel() { 117 | return resetCredentialsLabel; 118 | } 119 | 120 | public ErrorParameter setResetCredentialsLabel(String resetCredentialsLabel) { 121 | this.resetCredentialsLabel = resetCredentialsLabel; 122 | return this; 123 | } 124 | 125 | @Override public boolean equals(Object o) { 126 | if (this == o) return true; 127 | if (!(o instanceof ErrorParameter)) return false; 128 | final ErrorParameter that = (ErrorParameter) o; 129 | return getStatus() == that.getStatus() && 130 | Objects.equals(getBase(), that.getBase()) && 131 | Objects.equals(getResponseData(), that.getResponseData()) && 132 | Objects.equals(getResponseDataLabel(), that.getResponseDataLabel()) && 133 | Objects.equals(getStack(), that.getStack()) && 134 | Objects.equals(getSomethingWentWrongLabel(), that.getSomethingWentWrongLabel()) && 135 | Objects.equals(getTryLabel(), that.getTryLabel()) && 136 | Objects.equals(getStackTraceLabel(), that.getStackTraceLabel()) && 137 | Objects.equals(getResetCredentialsLabel(), that.getResetCredentialsLabel()) && 138 | Objects.equals(usesCustomCredentials(), that.usesCustomCredentials()) && 139 | Objects.equals(getHints(), that.getHints()); 140 | } 141 | 142 | @Override public int hashCode() { 143 | return Objects.hash(getBase(), getResponseData(), getStack(), getStackTraceLabel(), getSomethingWentWrongLabel(), getStackTraceLabel(), getStatus(), getTryLabel()); 144 | } 145 | 146 | @Override public String toString() { 147 | return "ErrorParameter { " + super.toString() + " " 148 | + "base = " + getBase() + ", " 149 | + "responseData = " + getResponseData() + ", " 150 | + "stack = " + getStack() + ", " 151 | + "somethingWentWrongLabel = " + getSomethingWentWrongLabel() + ", " 152 | + "stackTraceLabel = " + getStackTraceLabel() + ", " 153 | + "status = " + getStatus() + ", " 154 | + "hints = " + String.join(",", hints) + ", " 155 | + "tryLabel= " + getTryLabel() + " " 156 | + "}"; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/contentful/tea/java/models/courses/CourseParameter.java: -------------------------------------------------------------------------------- 1 | package com.contentful.tea.java.models.courses; 2 | 3 | import com.contentful.tea.java.models.base.BaseParameter; 4 | import com.contentful.tea.java.models.mappable.MappableType; 5 | 6 | import java.util.Objects; 7 | 8 | public class CourseParameter extends MappableType { 9 | private BaseParameter base = new BaseParameter(); 10 | 11 | private String tableOfContentsLabel; 12 | private String skillLevelLabel; 13 | private String overviewLabel; 14 | private String courseOverviewLabel; 15 | private String courseOverviewCssClass; 16 | private String durationLabel; 17 | private String minutesLabel; 18 | private String startCourseLabel; 19 | private String nextLessonLabel; 20 | private Course course; 21 | 22 | public BaseParameter getBase() { 23 | return base; 24 | } 25 | 26 | public String getTableOfContentsLabel() { 27 | return tableOfContentsLabel; 28 | } 29 | 30 | public CourseParameter setTableOfContentsLabel(String tableOfContentsLabel) { 31 | this.tableOfContentsLabel = tableOfContentsLabel; 32 | return this; 33 | } 34 | 35 | public String getSkillLevelLabel() { 36 | return skillLevelLabel; 37 | } 38 | 39 | public CourseParameter setSkillLevelLabel(String skillLevelLabel) { 40 | this.skillLevelLabel = skillLevelLabel; 41 | return this; 42 | } 43 | 44 | public String getOverviewLabel() { 45 | return overviewLabel; 46 | } 47 | 48 | public CourseParameter setOverviewLabel(String overviewLabel) { 49 | this.overviewLabel = overviewLabel; 50 | return this; 51 | } 52 | 53 | public String getCourseOverviewLabel() { 54 | return courseOverviewLabel; 55 | } 56 | 57 | public CourseParameter setCourseOverviewLabel(String courseOverviewLabel) { 58 | this.courseOverviewLabel = courseOverviewLabel; 59 | return this; 60 | } 61 | 62 | public String getCourseOverviewCssClass() { 63 | return courseOverviewCssClass; 64 | } 65 | 66 | public CourseParameter setCourseOverviewCssClass(String courseOverviewCssClass) { 67 | this.courseOverviewCssClass = courseOverviewCssClass; 68 | return this; 69 | } 70 | 71 | public String getDurationLabel() { 72 | return durationLabel; 73 | } 74 | 75 | public CourseParameter setDurationLabel(String durationLabel) { 76 | this.durationLabel = durationLabel; 77 | return this; 78 | } 79 | 80 | public String getMinutesLabel() { 81 | return minutesLabel; 82 | } 83 | 84 | public CourseParameter setMinutesLabel(String minutesLabel) { 85 | this.minutesLabel = minutesLabel; 86 | return this; 87 | } 88 | 89 | public String getStartCourseLabel() { 90 | return startCourseLabel; 91 | } 92 | 93 | public CourseParameter setStartCourseLabel(String startCourseLabel) { 94 | this.startCourseLabel = startCourseLabel; 95 | return this; 96 | } 97 | 98 | public Course getCourse() { 99 | return course; 100 | } 101 | 102 | public CourseParameter setCourse(Course course) { 103 | this.course = course; 104 | return this; 105 | } 106 | 107 | public String getNextLessonLabel() { 108 | return nextLessonLabel; 109 | } 110 | 111 | public CourseParameter setNextLessonLabel(String nextLessonLabel) { 112 | this.nextLessonLabel = nextLessonLabel; 113 | return this; 114 | } 115 | 116 | @Override public boolean equals(Object o) { 117 | if (this == o) return true; 118 | if (!(o instanceof CourseParameter)) return false; 119 | final CourseParameter that = (CourseParameter) o; 120 | return Objects.equals(getTableOfContentsLabel(), that.getTableOfContentsLabel()) && 121 | Objects.equals(getSkillLevelLabel(), that.getSkillLevelLabel()) && 122 | Objects.equals(getCourseOverviewLabel(), that.getCourseOverviewLabel()) && 123 | Objects.equals(getCourseOverviewCssClass(), that.getCourseOverviewCssClass()) && 124 | Objects.equals(getOverviewLabel(), that.getOverviewLabel()) && 125 | Objects.equals(getDurationLabel(), that.getDurationLabel()) && 126 | Objects.equals(getMinutesLabel(), that.getMinutesLabel()) && 127 | Objects.equals(getStartCourseLabel(), that.getStartCourseLabel()) && 128 | Objects.equals(getNextLessonLabel(), that.getNextLessonLabel()) && 129 | Objects.equals(getCourse(), that.getCourse()); 130 | } 131 | 132 | @Override public int hashCode() { 133 | return Objects.hash(getTableOfContentsLabel(), getSkillLevelLabel(), getCourseOverviewLabel(), getCourseOverviewCssClass(), getOverviewLabel(), getDurationLabel(), getMinutesLabel(), getStartCourseLabel(), getNextLessonLabel(), getCourse()); 134 | } 135 | 136 | @Override public String toString() { 137 | return "CourseParameter { " + super.toString() + " " 138 | + "durationLabel = " + getDurationLabel() + ", " 139 | + "course = " + getCourse() + ", " 140 | + "minutesLabel = " + getMinutesLabel() + ", " 141 | + "courseOverViewLabel = " + getCourseOverviewLabel() + ", " 142 | + "courseOverviewCssClass = " + getCourseOverviewCssClass() + ", " 143 | + "overviewLabel = " + getOverviewLabel() + ", " 144 | + "skillLevelLabel = " + getSkillLevelLabel() + ", " 145 | + "startCourseLabel = " + getStartCourseLabel() + ", " 146 | + "tableOfContentsLabel = " + getTableOfContentsLabel() + ", " 147 | + "nextLessonLabel= " + getNextLessonLabel() + " " 148 | + "}"; 149 | } 150 | } 151 | --------------------------------------------------------------------------------