├── README.md ├── Procfile ├── web ├── bg.jpeg ├── star.png ├── weCodeInPeace.png ├── style.less └── index.html ├── .gitignore ├── runtest.sh ├── src ├── main │ └── java │ │ ├── TopFight.java │ │ ├── Scorer.java │ │ ├── JerseyModule.java │ │ ├── Votes.java │ │ ├── FightServer.java │ │ ├── Talks.java │ │ ├── TopFights.java │ │ └── FightResource.java └── test │ ├── java │ ├── VotesTest.java │ ├── ScorerTest.java │ ├── FightServerTest.java │ ├── TopFightsTest.java │ └── TalksTest.java │ ├── resources │ └── starsPerTalk.json │ └── acceptance │ └── acceptance_test.coffee ├── etc ├── junit_run_mocha.sh └── push.sh └── pom.xml /README.md: -------------------------------------------------------------------------------- 1 | This is the project coded live at Devoxx World on November 14th&15th. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java -Xmx512M -cp target/classes:target/dependency/* FightServer 2 | -------------------------------------------------------------------------------- /web/bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeStory/code-story-fight/master/web/bg.jpeg -------------------------------------------------------------------------------- /web/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeStory/code-story-fight/master/web/star.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | target/ 3 | .idea/ 4 | .DS_Store 5 | prod.log 6 | fightCount.bin 7 | -------------------------------------------------------------------------------- /web/weCodeInPeace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeStory/code-story-fight/master/web/weCodeInPeace.png -------------------------------------------------------------------------------- /runtest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mocha --reporter spec --compilers coffee:coffee-script --timeout 5000 src/test/acceptance/*.coffee $@ 3 | -------------------------------------------------------------------------------- /src/main/java/TopFight.java: -------------------------------------------------------------------------------- 1 | import lombok.Data; 2 | 3 | import java.io.Serializable; 4 | 5 | @Data 6 | public class TopFight implements Serializable { 7 | private final String left; 8 | private final String right; 9 | } 10 | -------------------------------------------------------------------------------- /etc/junit_run_mocha.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -n "$2" ]; then 3 | export PORT=$2 4 | fi 5 | export NODE_PATH=/usr/local/share/npm/lib/node_modules 6 | 7 | /usr/local/share/npm/bin/mocha --no-colors --compilers coffee:coffee-script --timeout 10000 $1 8 | #> /dev/null 2>&1 9 | -------------------------------------------------------------------------------- /src/test/java/VotesTest.java: -------------------------------------------------------------------------------- 1 | import com.google.common.io.Resources; 2 | import org.junit.Test; 3 | 4 | import static org.fest.assertions.Assertions.assertThat; 5 | 6 | public class VotesTest { 7 | @Test 8 | public void should_sum_up_scores_with_a_real_url() { 9 | Votes scores = new Votes(Resources.getResource("starsPerTalk.json")); 10 | 11 | int score = scores.score(931, 805); 12 | 13 | assertThat(score).isEqualTo(37); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/Scorer.java: -------------------------------------------------------------------------------- 1 | import com.google.inject.Inject; 2 | import com.google.inject.Singleton; 3 | 4 | @Singleton 5 | public class Scorer { 6 | private final Talks talkIds; 7 | private final Votes votes; 8 | 9 | @Inject 10 | public Scorer(Talks talkIds, Votes votes) { 11 | this.talkIds = talkIds; 12 | this.votes = votes; 13 | } 14 | 15 | protected int get(String keyword) { 16 | int[] ids = talkIds.ids(keyword); 17 | return votes.score(ids); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/ScorerTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Test; 2 | 3 | import static org.fest.assertions.Assertions.assertThat; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.when; 6 | 7 | public class ScorerTest { 8 | Talks talkIds = mock(Talks.class); 9 | Votes votes = mock(Votes.class); 10 | Scorer scorer = new Scorer(talkIds, votes); 11 | 12 | @Test 13 | public void should_compute_scores_for_two_keywords() { 14 | when(talkIds.ids("angularJs")).thenReturn(new int[]{1, 2}); 15 | when(talkIds.ids("java")).thenReturn(new int[]{3, 4, 5}); 16 | when(votes.score(1, 2)).thenReturn(100); 17 | when(votes.score(3, 4, 5)).thenReturn(66); 18 | 19 | assertThat(scorer.get("angularJs")).isEqualTo(100); 20 | assertThat(scorer.get("java")).isEqualTo(66); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/JerseyModule.java: -------------------------------------------------------------------------------- 1 | import com.google.inject.AbstractModule; 2 | 3 | import java.net.MalformedURLException; 4 | import java.net.URL; 5 | 6 | import static com.google.inject.name.Names.named; 7 | 8 | public class JerseyModule extends AbstractModule { 9 | private final static String PLANNING_JSON = "http://planning.code-story.net/planning.json"; 10 | private final static String VOTES_JSON = "http://planning.code-story.net/starsPerTalk"; 11 | 12 | @Override 13 | protected void configure() { 14 | try { 15 | bind(URL.class).annotatedWith(named("planningUrl")).toInstance(new URL(PLANNING_JSON)); 16 | } catch (MalformedURLException e) { 17 | addError(e); 18 | } 19 | 20 | try { 21 | bind(URL.class).annotatedWith(named("votesUrl")).toInstance(new URL(VOTES_JSON)); 22 | } catch (MalformedURLException e) { 23 | addError(e); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/FightServerTest.java: -------------------------------------------------------------------------------- 1 | import com.google.common.io.Resources; 2 | import com.google.inject.AbstractModule; 3 | import net.gageot.test.rules.ServiceRule; 4 | import net.gageot.test.utils.Shell; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import java.net.URL; 9 | 10 | import static com.google.inject.name.Names.named; 11 | import static org.fest.assertions.Assertions.assertThat; 12 | 13 | public class FightServerTest { 14 | @Rule 15 | public ServiceRule server = ServiceRule.startWithRandomPort(FightServer.class, new AbstractModule() { 16 | @Override 17 | protected void configure() { 18 | bind(URL.class).annotatedWith(named("planningUrl")).toInstance(Resources.getResource("planning.json")); 19 | bind(URL.class).annotatedWith(named("votesUrl")).toInstance(Resources.getResource("starsPerTalk.json")); 20 | } 21 | }); 22 | 23 | @Test 24 | public void should_test_home_page() throws Exception { 25 | int resultCode = new Shell().execute("./etc/junit_run_mocha.sh ./src/test/acceptance/acceptance_test.coffee %d", server.getPort()); 26 | 27 | assertThat(resultCode).isZero(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/Votes.java: -------------------------------------------------------------------------------- 1 | import com.google.common.base.Charsets; 2 | import com.google.common.io.Resources; 3 | import com.google.gson.Gson; 4 | import com.google.gson.reflect.TypeToken; 5 | import com.google.inject.Inject; 6 | import com.google.inject.Singleton; 7 | import com.google.inject.name.Named; 8 | 9 | import java.io.IOException; 10 | import java.lang.reflect.Type; 11 | import java.net.URL; 12 | import java.util.Map; 13 | 14 | import static com.google.common.base.Objects.firstNonNull; 15 | 16 | @Singleton 17 | public class Votes { 18 | private final URL url; 19 | 20 | @Inject 21 | public Votes(@Named("votesUrl") URL url) { 22 | this.url = url; 23 | } 24 | 25 | public int score(int... talkIds) { 26 | Map votes = parseVotes(); 27 | 28 | int sum = 0; 29 | for (int talkId : talkIds) { 30 | sum += firstNonNull(votes.get(talkId), 0); 31 | } 32 | return sum; 33 | } 34 | 35 | private Map parseVotes() { 36 | Type typeToken = new TypeToken>() { 37 | }.getType(); 38 | 39 | 40 | try { 41 | return new Gson().fromJson(Resources.toString(url, Charsets.UTF_8), typeToken); 42 | } catch (IOException e) { 43 | throw new IllegalStateException("Unable to read votes json", e); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/FightServer.java: -------------------------------------------------------------------------------- 1 | import com.google.inject.Guice; 2 | import com.google.inject.Injector; 3 | import com.google.inject.Module; 4 | import com.sun.jersey.api.container.httpserver.HttpServerFactory; 5 | import com.sun.jersey.api.core.DefaultResourceConfig; 6 | import com.sun.jersey.api.core.ResourceConfig; 7 | import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory; 8 | import com.sun.jersey.guice.spi.container.GuiceComponentProviderFactory; 9 | import com.sun.net.httpserver.HttpServer; 10 | 11 | import java.io.IOException; 12 | 13 | import static com.google.common.base.Objects.firstNonNull; 14 | import static com.google.inject.util.Modules.override; 15 | import static java.lang.Integer.parseInt; 16 | import static java.lang.String.format; 17 | 18 | public class FightServer { 19 | final Injector injector; 20 | HttpServer httpServer; 21 | 22 | public FightServer(Module... modules) { 23 | injector = Guice.createInjector(override(new JerseyModule()).with(modules)); 24 | } 25 | 26 | public static void main(String[] args) throws IOException { 27 | int port = parseInt(firstNonNull(System.getenv("PORT"), "8080")); 28 | 29 | new FightServer().start(port); 30 | } 31 | 32 | public void start(int port) throws IOException { 33 | ResourceConfig config = new DefaultResourceConfig(FightResource.class); 34 | IoCComponentProviderFactory ioc = new GuiceComponentProviderFactory(config, injector); 35 | 36 | httpServer = HttpServerFactory.create(format("http://localhost:%d/", port), config, ioc); 37 | httpServer.start(); 38 | } 39 | 40 | public void stop() { 41 | httpServer.stop(0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/TopFightsTest.java: -------------------------------------------------------------------------------- 1 | import org.junit.Before; 2 | import org.junit.Test; 3 | 4 | import static org.fest.assertions.Assertions.assertThat; 5 | 6 | public class TopFightsTest { 7 | private TopFights topFights; 8 | 9 | @Before 10 | public void init() { 11 | topFights = new TopFights(false); 12 | } 13 | 14 | @Test 15 | public void should_increment_keyword_counters() throws Exception { 16 | topFights.log("AngularJS", "Jabba The Hutt"); 17 | TopFight topFight = topFights.first(); 18 | 19 | assertThat(topFight.getLeft()).isEqualTo("AngularJS"); 20 | assertThat(topFight.getRight()).isEqualTo("Jabba The Hutt"); 21 | } 22 | 23 | @Test 24 | public void should_increment_twice_and_return_the_right_topFight() throws Exception { 25 | topFights.log("Polka", "Rock N Roll"); 26 | topFights.log("JavaFX", "Hibernate"); 27 | topFights.log("AngularJS", "Jabba The Hutt"); 28 | topFights.log("AngularJS", "Jabba The Hutt"); 29 | topFights.log("AngularJS", "Jabba The Hutt");// <-- we did that 3 times, right. 30 | topFights.log("JavaFX", "Hibernate"); 31 | 32 | TopFight topFight = topFights.first(); 33 | 34 | assertThat(topFight.getLeft()).isEqualTo("AngularJS"); 35 | assertThat(topFight.getRight()).isEqualTo("Jabba The Hutt"); 36 | } 37 | 38 | @Test 39 | public void should_save_and_read() throws Exception { 40 | topFights.log("Polka", "Rock N Roll"); 41 | topFights.saveOnDisk(); 42 | 43 | TopFights topFights2 = new TopFights(); 44 | 45 | TopFight topFight = topFights2.first(); 46 | 47 | assertThat(topFight.getLeft()).isEqualTo("Polka"); 48 | assertThat(topFight.getRight()).isEqualTo("Rock N Roll"); 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/TalksTest.java: -------------------------------------------------------------------------------- 1 | import com.google.common.collect.ImmutableMap; 2 | import com.google.common.io.Resources; 3 | import org.junit.Test; 4 | 5 | import java.util.Set; 6 | 7 | import static java.util.Arrays.asList; 8 | import static org.fest.assertions.Assertions.assertThat; 9 | 10 | public class TalksTest { 11 | @Test 12 | public void should_find_ids_from_keywords() { 13 | Talks talks = new Talks(ImmutableMap.of( 14 | 1, asList("javafx", "keyword"), 15 | 2, asList("AngularJS", "anotherKeyword"), 16 | 3, asList("nothing"))); 17 | 18 | int[] ids = talks.ids("javafx"); 19 | 20 | assertThat(ids).containsOnly(1); 21 | } 22 | 23 | @Test 24 | public void should_find_ids_from_keywords_with_different_case() { 25 | Talks talks = new Talks(ImmutableMap.of( 26 | 42, asList("javaFX"))); 27 | 28 | int[] ids = talks.ids("JAVAfx"); 29 | 30 | assertThat(ids).containsOnly(42); 31 | } 32 | 33 | @Test 34 | public void should_read_planning_as_json() { 35 | Talks talks = new Talks(Resources.getResource("planning.json")); 36 | 37 | int[] ids = talks.ids("AngularJs"); 38 | 39 | assertThat(ids).containsOnly(931, 805); 40 | } 41 | 42 | @Test 43 | public void should_search_on_all_metadata() { 44 | Talks talks = new Talks(Resources.getResource("planning.json")); 45 | 46 | int[] ids = talks.ids("JavaFX"); 47 | 48 | assertThat(ids).hasSize(13); 49 | } 50 | 51 | @Test 52 | public void should_extract_words_from_planning() { 53 | Talks talks = new Talks(Resources.getResource("planning.json")); 54 | 55 | Set extractedWords = talks.extractWords(); 56 | 57 | assertThat(extractedWords).contains("maven").excludes("Maven"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/resources/starsPerTalk.json: -------------------------------------------------------------------------------- 1 | {"779":2, "948":5, "866":3, "838":14, "964":8, "858":19, "872":7, "853":1, "831":19, "974":3, "959":7, "920":7, "971":5, "849":18, "759":11, "763":6, "806":9, "894":6, "834":11, "878":3, "986":7, "836":5, "912":3, "907":4, "839":1, "885":4, "935":16, "764":15, "860":6, "956":4, "965":7, "781":7, "854":2, "773":12, "813":15, "944":10, "798":5, "818":5, "972":1, "807":14, "909":4, "893":3, "928":1, "825":3, "868":4, "884":4, "957":17, "966":3, "943":8, "788":4, "785":12, "877":13, "812":3, "815":3, "897":6, "778":8, "906":5, "865":7, "934":9, "796":5, "911":4, "766":2, "799":13, "891":4, "846":4, "792":6, "937":7, "953":2, "896":2, "810":2, "840":8, "979":1, "787":11, "869":4, "862":2, "913":6, "827":4, "880":4, "978":4, "905":1, "976":10, "936":9, "927":9, "942":2, "883":7, "805":18, "983":6, "876":6, "780":1, "848":2, "914":12, "771":6, "982":11, "856":3, "859":2, "864":6, "892":5, "817":12, "767":10, "926":1, "881":3, "794":3, "843":3, "852":4, "824":1, "882":2, "975":3, "842":8, "954":8, "875":6, "904":10, "830":16, "970":4, "851":7, "867":7, "985":3, "939":7, "873":3, "930":7, "789":11, "960":3, "915":11, "919":13, "768":13, "940":1, "837":9, "898":3, "870":7, "889":10, "874":13, "967":4, "845":6, "888":12, "770":21, "916":13, "918":2, "847":7, "980":2, "790":2, "797":12, "961":3, "945":3, "784":15, "760":3, "791":7, "938":4, "861":4, "932":2, "901":1, "774":10, "823":6, "984":8, "850":3, "955":3, "819":3, "826":3, "910":1, "808":9, "835":1, "761":8, "814":1, "968":6, "887":16, "922":6, "832":18, "783":4, "977":3, "947":3, "820":8, "900":9, "931":19, "924":5, "890":4, "844":5, "857":11, "828":4, "969":3, "973":8, "776":5, "902":15, "786":5, "963":11, "958":6, "775":16, "762":14, "809":8, "795":3, "769":2} -------------------------------------------------------------------------------- /etc/push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function alert_user { 4 | echo "${1}" 5 | which -s growlnotify && growlnotify `basename $0` -m "${1}" 6 | } 7 | 8 | function exit_ko { 9 | alert_user "${1}"; exit 1 10 | } 11 | 12 | function exit_ok { 13 | alert_user "${1}"; exit 0 14 | } 15 | 16 | LOCATION=$(pwd) 17 | REMOTE=${1:-origin} 18 | REMOTE_URL=$(git remote show -n ${REMOTE} | awk '/Fetch/ {print $3}') 19 | BRANCH=$(git symbolic-ref -q HEAD) 20 | BRANCH=${BRANCH##refs/heads/} 21 | 22 | # Get command for build from Git config 23 | # git config private-build.command "mvn install" 24 | # git config --remove-section private-build 25 | # 26 | COMMAND=$(git config --get private-build.command) 27 | if [ -z "${COMMAND}" ]; then 28 | COMMAND="mvn -V -T4 clean install -Pdev -DskipSanityChecks=false" 29 | fi 30 | 31 | # Git black magic to pull rebase even with uncommited work in progress 32 | git fetch ${REMOTE} 33 | git add -A; git ls-files --deleted -z | xargs -0 -I {} git rm {}; git commit -m "wip" 34 | git rebase ${REMOTE}/${BRANCH} 35 | 36 | if [ "$?" -ne 0 ]; then 37 | git rebase --abort 38 | git log -n 1 | grep -q -c "wip" && git reset HEAD~1 39 | exit_ko "Unable to rebase. please pull or rebase and fix conflicts manually." 40 | fi 41 | git log -n 1 | grep -q -c "wip" && git reset HEAD~1 42 | 43 | # Private build 44 | rm -Rf ../privatebuild 45 | git clone --single-branch -slb "${BRANCH}" . ../privatebuild 46 | cd ../privatebuild 47 | 48 | # Build 49 | eval ${COMMAND} 50 | if [ $? -ne 0 ]; then 51 | exit_ko "Unable to build" 52 | fi 53 | 54 | # Push 55 | git push ${REMOTE_URL} ${BRANCH} 56 | if [ $? -ne 0 ]; then 57 | exit_ko "Unable to push" 58 | fi 59 | 60 | # Update working directory 61 | cd ${LOCATION} && git fetch ${REMOTE} 62 | exit_ok "Yet another successful build!" 63 | -------------------------------------------------------------------------------- /src/test/acceptance/acceptance_test.coffee: -------------------------------------------------------------------------------- 1 | Browser = require('zombie') 2 | should = require('chai').should() 3 | 4 | port = process.env.PORT || 8080 5 | home = "http://127.0.0.1:#{port}" 6 | 7 | describe 'Server', -> 8 | it 'should show the homepage', (done) -> 9 | browser = new Browser 10 | browser.visit "#{home}/", -> 11 | browser.text('title').should.equal 'Code-Story Fight for Devoxx' 12 | done() 13 | 14 | it 'should display scores for AngularJS and JavaFX', (done) -> 15 | browser = new Browser 16 | browser.visit "#{home}/fight/AngularJS/JavaFX", -> 17 | browser.field('#leftKeyword').value.should.equal 'AngularJS' 18 | browser.field('#rightKeyword').value.should.equal 'JavaFX' 19 | browser.text('#leftScore').should.contain '37' 20 | browser.text('#rightScore').should.contain '51' 21 | done() 22 | 23 | it 'should display scores for java and scala', (done) -> 24 | browser = new Browser 25 | browser.visit "#{home}/fight/java/scala", -> 26 | browser.field('input[id="leftKeyword"]').value.should.equal 'java' 27 | browser.field('input[id="rightKeyword"]').value.should.equal 'scala' 28 | browser.text('#leftScore').should.contain '819' 29 | browser.text('#rightScore').should.contain '207' 30 | done() 31 | 32 | it 'should display the top fight in the home page', (done) -> 33 | browser = new Browser 34 | browser.visit "#{home}/", -> 35 | browser.text('#topFight li:nthchild(1)').should.equal 'AngularJS vs JavaFX' 36 | browser.query('#topFight li:nthchild(1) a').href.should.equal "#{home}/fight/AngularJS/JavaFX" 37 | done() 38 | 39 | it 'should always loose against CodeStory', (done) -> 40 | browser = new Browser 41 | browser.visit "#{home}/fight/world/codestory", -> 42 | browser.text('#leftScore').should.contain '152' 43 | browser.text('#rightScore').should.contain '162' 44 | done() 45 | -------------------------------------------------------------------------------- /src/main/java/Talks.java: -------------------------------------------------------------------------------- 1 | import com.google.common.annotations.VisibleForTesting; 2 | import com.google.common.base.Charsets; 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.Maps; 5 | import com.google.common.collect.Sets; 6 | import com.google.common.io.Resources; 7 | import com.google.common.primitives.Ints; 8 | import com.google.gson.Gson; 9 | import com.google.inject.Inject; 10 | import com.google.inject.Singleton; 11 | import com.google.inject.name.Named; 12 | 13 | import java.io.IOException; 14 | import java.net.URL; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | @Singleton 20 | public class Talks { 21 | private final Map> keywordsByIds; 22 | private final Set words = Sets.newTreeSet(); 23 | 24 | @VisibleForTesting 25 | Talks(Map> keywordsByIds) { 26 | this.keywordsByIds = keywordsByIds; 27 | } 28 | 29 | @Inject 30 | public Talks(@Named("planningUrl") URL url) { 31 | Days days; 32 | try { 33 | days = new Gson().fromJson(Resources.toString(url, Charsets.UTF_8), Days.class); 34 | } catch (IOException e) { 35 | throw new IllegalStateException("Unable to read planning json", e); 36 | } 37 | 38 | keywordsByIds = Maps.newHashMap(); 39 | for (Day day : days.days) { 40 | for (Slot slot : day.slots) { 41 | for (Talk talk : slot.talks) { 42 | keywordsByIds.put(talk.id, ImmutableList.builder() 43 | .add(talk.title) 44 | .add(talk.summary) 45 | .addAll(talk.tags) 46 | .addAll(talk.speakers) 47 | .build()); 48 | 49 | for (String tag : talk.tags) { 50 | words.add(tag.toLowerCase()); 51 | } 52 | for (String tag : talk.speakers) { 53 | words.add(tag.toLowerCase()); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | public Set extractWords() { 61 | return words; 62 | } 63 | 64 | public int[] ids(String keyword) { 65 | Set ids = Sets.newHashSet(); 66 | 67 | for (Map.Entry> keywordsForId : keywordsByIds.entrySet()) { 68 | for (String talkKeyword : keywordsForId.getValue()) { 69 | if (talkKeyword.toLowerCase().contains(keyword.toLowerCase())) { 70 | ids.add(keywordsForId.getKey()); 71 | } 72 | } 73 | } 74 | 75 | return Ints.toArray(ids); 76 | } 77 | 78 | static class Days { 79 | List days; 80 | } 81 | 82 | static class Day { 83 | List slots; 84 | } 85 | 86 | static class Slot { 87 | List talks; 88 | } 89 | 90 | static class Talk { 91 | int id; 92 | String title; 93 | String summary; 94 | List tags; 95 | List speakers; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/TopFights.java: -------------------------------------------------------------------------------- 1 | import com.google.common.collect.Lists; 2 | import com.google.common.util.concurrent.AtomicLongMap; 3 | import com.sun.org.apache.xpath.internal.SourceTree; 4 | 5 | import javax.inject.Singleton; 6 | import java.io.ByteArrayInputStream; 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.ObjectInputStream; 11 | import java.io.ObjectOutputStream; 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.Comparator; 15 | import java.util.LinkedHashSet; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.Set; 19 | 20 | import static com.google.common.collect.ImmutableMap.copyOf; 21 | import static com.google.common.io.Files.toByteArray; 22 | import static com.google.common.io.Files.write; 23 | import static com.google.common.util.concurrent.AtomicLongMap.create; 24 | 25 | @Singleton 26 | public class TopFights { 27 | private AtomicLongMap fightCount; 28 | private File outputFile; 29 | 30 | public TopFights() { 31 | init(true); 32 | } 33 | 34 | /*package*/ TopFights(boolean doReadFile) { 35 | init(doReadFile); 36 | } 37 | 38 | private void init(boolean readFile) { 39 | fightCount = create(); 40 | outputFile = new File("fightCount.bin"); 41 | if (readFile) { 42 | loadFromDisk(); 43 | } 44 | } 45 | 46 | public TopFight first() { 47 | return get().get(0); 48 | } 49 | 50 | public List get() { 51 | Set> topFightEntries = fightCount.asMap().entrySet(); 52 | ArrayList> entries = Lists.newArrayList(topFightEntries); 53 | Collections.sort(entries, new Comparator>() { 54 | @Override 55 | public int compare(Map.Entry o1, Map.Entry o2) { 56 | return Long.compare(o2.getValue(), o1.getValue()); 57 | } 58 | }); 59 | 60 | List topFights = new ArrayList<>(); 61 | 62 | for (Map.Entry topFightLongEntry : entries) { 63 | topFights.add(topFightLongEntry.getKey()); 64 | if (topFights.size() == 5) { 65 | return topFights; 66 | } 67 | } 68 | 69 | return topFights; 70 | } 71 | 72 | public void log(String left, String right) { 73 | fightCount.incrementAndGet(new TopFight(left, right)); 74 | saveOnDisk(); 75 | } 76 | 77 | public synchronized void loadFromDisk() { 78 | if (!outputFile.exists()) { 79 | return; 80 | } 81 | try { 82 | byte[] raw = toByteArray(outputFile); 83 | ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(raw)); 84 | fightCount = create((Map) ois.readObject()); 85 | ois.close(); 86 | } catch (ClassNotFoundException | IOException exception) { 87 | throw new RuntimeException(exception.getMessage()); 88 | } 89 | } 90 | 91 | public synchronized void saveOnDisk() { 92 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 93 | try { 94 | ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream); 95 | oos.writeObject(copyOf(fightCount.asMap())); 96 | oos.flush(); 97 | write(byteArrayOutputStream.toByteArray(), outputFile); 98 | } catch (IOException ioe) { 99 | ioe.printStackTrace(System.err); 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | codestory 8 | fight 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 1.7 13 | 1.7 14 | UTF-8 15 | 16 | 17 | 18 | 19 | 20 | maven-dependency-plugin 21 | 22 | 23 | copy-dependencies 24 | install 25 | 26 | copy-dependencies 27 | 28 | 29 | compile 30 | runtime 31 | test 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | com.github.spullara.mustache.java 42 | compiler 43 | 0.8.7 44 | 45 | 46 | com.google.code.gson 47 | gson 48 | 2.2.2 49 | 50 | 51 | net.gageot 52 | testexpert 53 | 1.0.5 54 | test 55 | 56 | 57 | junit 58 | junit 59 | 4.10 60 | test 61 | 62 | 63 | com.google.guava 64 | guava 65 | 13.0.1 66 | 67 | 68 | org.easytesting 69 | fest-assert 70 | 1.4 71 | test 72 | 73 | 74 | org.mockito 75 | mockito-all 76 | 1.9.5-rc1 77 | test 78 | 79 | 80 | com.sun.jersey 81 | jersey-server 82 | 1.12 83 | 84 | 85 | com.google.inject 86 | guice 87 | 3.0 88 | 89 | 90 | com.sun.jersey.contribs 91 | jersey-guice 92 | 1.12 93 | 94 | 95 | org.projectlombok 96 | lombok 97 | 0.11.4 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /web/style.less: -------------------------------------------------------------------------------- 1 | body { 2 | background: url('images/bg.jpeg'); 3 | background-size: cover; 4 | font-family: sans-serif; 5 | text-align: center; 6 | 7 | a { 8 | color: white; 9 | } 10 | 11 | h1 { 12 | color: white; 13 | } 14 | 15 | #topFight { 16 | margin-top:100px; 17 | margin-left:auto; 18 | margin-right:auto; 19 | 20 | h2 { 21 | color:white; 22 | } 23 | ul { 24 | padding:0; 25 | } 26 | 27 | li { 28 | font-size:200%; 29 | font-weight: bold; 30 | list-style-type:none; 31 | } 32 | 33 | } 34 | 35 | #fight { 36 | position: relative; 37 | width: 779px; 38 | height: 200px; 39 | margin: 50px auto 0 auto; 40 | font-size: 200%; 41 | 42 | .left(){ 43 | position: absolute; 44 | left: 0; 45 | } 46 | .right(){ 47 | position: absolute; 48 | right: 0; 49 | } 50 | 51 | #leftKeyword, #rightKeyword { 52 | font-size: 1em; 53 | margin: 0; 54 | width: 301px; 55 | border-radius: 5px; 56 | padding: 10px; 57 | } 58 | 59 | #leftKeyword { 60 | .left; 61 | top: 0; 62 | } 63 | 64 | #rightKeyword { 65 | .right; 66 | top: 0; 67 | } 68 | 69 | #letsfight { 70 | .left; 71 | font-size: 1em; 72 | margin: 0; 73 | top: 0; 74 | left: 334px; 75 | } 76 | 77 | #leftScore { 78 | .left; 79 | top: 80px; 80 | left: 80px; 81 | } 82 | 83 | #rightScore { 84 | .right; 85 | top: 80px; 86 | right: 80px; 87 | } 88 | 89 | @scale: 2; 90 | @star_width: 300px / @scale; 91 | @star_height: 286px / @scale; 92 | 93 | #leftScore, #rightScore { 94 | display: block; 95 | width: @star_width; 96 | height: (@star_height)-5px; 97 | background: url('images/star.png'); 98 | background-size: @star_width @star_height; 99 | line-height: @star_height; 100 | vertical-align: middle; 101 | color: black; 102 | padding-top: 5px; 103 | } 104 | 105 | .winner { 106 | .scaleout(scale-winner); 107 | } 108 | 109 | .looser { 110 | .scaleout(scale-looser); 111 | } 112 | 113 | .scaleout(@name) { 114 | -webkit-animation: @name 1s ease-out; 115 | animation: @name 1s ease-out; 116 | -webkit-animation-fill-mode: forwards; 117 | animation-fill-mode: forwards; 118 | } 119 | 120 | #leftKeyword { 121 | margin-right: 10px; 122 | } 123 | 124 | #letsfight { 125 | padding: 10px; 126 | } 127 | 128 | #rightKeyword { 129 | text-align: right; 130 | } 131 | 132 | .twitter-share-button { 133 | margin-top: 150px; 134 | } 135 | } 136 | } 137 | 138 | footer { 139 | background-color:#2E2E2E; 140 | border-color: #D9BF8C; 141 | border-width: 1px; 142 | border-style: dashed; 143 | color: #D9BF8C; 144 | margin-top:50px; 145 | text-align: center; 146 | a { 147 | color: #669199; 148 | } 149 | } 150 | 151 | .scaleoutFrames(@from, @to) { 152 | from { 153 | -webkit-transform: scale(@from); 154 | transform: scale(@from); 155 | } 156 | to { 157 | -webkit-transform: scale(@to); 158 | transform: scale(@to); 159 | } 160 | } 161 | 162 | @-webkit-keyframes scale-winner { .scaleoutFrames(0.0, 1.0); } 163 | @keyframes scale-winner { .scaleoutFrames(0.0, 1.0); } 164 | @-webkit-keyframes scale-looser { .scaleoutFrames(0.0, 0.5); } 165 | @keyframes scale-looser { .scaleoutFrames(0.0, 0.5); } 166 | -------------------------------------------------------------------------------- /src/main/java/FightResource.java: -------------------------------------------------------------------------------- 1 | import com.github.mustachejava.DefaultMustacheFactory; 2 | import com.github.mustachejava.Mustache; 3 | import com.google.common.collect.ImmutableMap; 4 | import com.google.gson.Gson; 5 | import com.google.inject.Inject; 6 | import com.google.inject.Singleton; 7 | 8 | import javax.ws.rs.GET; 9 | import javax.ws.rs.Path; 10 | import javax.ws.rs.PathParam; 11 | import javax.ws.rs.Produces; 12 | import javax.ws.rs.core.Response; 13 | import java.io.File; 14 | import java.io.StringWriter; 15 | import java.io.UnsupportedEncodingException; 16 | import java.net.URI; 17 | import java.util.Date; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | import static java.lang.String.format; 22 | 23 | @Singleton 24 | @Path("/") 25 | public class FightResource { 26 | private static final long ONE_MONTH = 1000L * 3600 * 24 * 30; 27 | 28 | private final Talks talks; 29 | private final Scorer scorer; 30 | private final TopFights topFights; 31 | 32 | @Inject 33 | public FightResource(Talks talks, Scorer scorer, TopFights topFights) { 34 | this.talks = talks; 35 | this.scorer = scorer; 36 | this.topFights = topFights; 37 | } 38 | 39 | @GET 40 | public Response index() { 41 | return Response.seeOther(URI.create("/fight/AngularJS/JavaFX")).build(); 42 | } 43 | 44 | @GET 45 | @Path("index.html") 46 | public Response oldIndex() { 47 | return index(); 48 | } 49 | 50 | @GET 51 | @Path("fight") 52 | public Response fight() { 53 | return index(); 54 | } 55 | 56 | @GET 57 | @Path("words") 58 | @Produces("application/javascript;charset=UTF-8") 59 | public String words() { 60 | return new Gson().toJson(talks.extractWords()); 61 | } 62 | 63 | @GET 64 | @Path("style.less") 65 | @Produces("text/css;charset=UTF-8") 66 | public File style() { 67 | return new File("web/style.less"); 68 | } 69 | 70 | @GET 71 | @Path("images/bg.jpeg") 72 | @Produces("image/jpeg") 73 | public Response background() { 74 | return staticResource("web/bg.jpeg"); 75 | } 76 | 77 | @GET 78 | @Path("images/{path : .*\\.png}") 79 | @Produces("image/png") 80 | public Response images(@PathParam("path") String path) { 81 | return staticResource("web/" + path); 82 | } 83 | 84 | @GET 85 | @Path("fight/{left}/{right}") 86 | @Produces("text/html;charset=UTF-8") 87 | public String fight(@PathParam("left") String leftKeyword, @PathParam("right") String rightKeyword) throws UnsupportedEncodingException { 88 | topFights.log(leftKeyword, rightKeyword); 89 | 90 | List topFight = topFights.get(); 91 | 92 | int leftScore = scorer.get(leftKeyword); 93 | int rightScore = scorer.get(rightKeyword); 94 | 95 | // Nasty hack 96 | if (leftKeyword.equalsIgnoreCase("CodeStory")) { 97 | leftScore = rightScore + 10; 98 | } 99 | if (rightKeyword.equalsIgnoreCase("CodeStory")) { 100 | rightScore = leftScore + 10; 101 | } 102 | 103 | Map templateData = ImmutableMap.builder() 104 | .put("leftKeyword", leftKeyword) 105 | .put("rightKeyword", rightKeyword) 106 | .put("leftScore", leftScore) 107 | .put("rightScore", rightScore) 108 | .put("fights", topFight) 109 | .put("url", format("http://fight.code-story.net/fight/%s/%s", leftKeyword, rightKeyword).replace(" ", "%20")) 110 | .build(); 111 | 112 | Mustache indexTemplate = new DefaultMustacheFactory().compile("web/index.html"); 113 | return indexTemplate.execute(new StringWriter(), templateData).toString(); 114 | } 115 | 116 | private static Response staticResource(String path) { 117 | File file = new File(path); 118 | long modified = file.lastModified(); 119 | 120 | return Response.ok(file).lastModified(new Date(modified)).expires(new Date(modified + ONE_MONTH)).build(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code-Story Fight for Devoxx 5 | 6 | 7 | 8 | 9 | 10 |

Code-Story Fight for Devoxx

11 | 12 |
13 | 14 | {{leftScore}} 16 | 17 | 18 | 19 | 20 | {{rightScore}} 22 | 23 | 26 |
27 | 28 |
29 |

Top Fights

30 | 37 |
38 | 39 |
40 |

This has been coded live during Devoxx World by the CodeStory team.

41 |

You can vote here to make your favorite keyword win.

42 |

You can check the source code of what we live coded.

43 |

The best experience is to see us live code: check for you next favorite tech conference, or contact us.

44 |
45 | 46 | 47 | 48 | 108 | 109 | --------------------------------------------------------------------------------