diff --git a/http-api/pom.xml b/http-api/pom.xml index cb7ad265e0..120db17783 100644 --- a/http-api/pom.xml +++ b/http-api/pom.xml @@ -50,6 +50,12 @@ slf4j-api 1.7.12 + + org.projectlombok + lombok + 1.16.18 + provided + junit diff --git a/http-api/src/main/java/net/runelite/http/api/xp/XpClient.java b/http-api/src/main/java/net/runelite/http/api/xp/XpClient.java new file mode 100644 index 0000000000..47a7ce31a4 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/xp/XpClient.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.xp; + +import java.io.IOException; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.HttpUrl; +import okhttp3.Request; + +public class XpClient +{ + public void update(String username) throws IOException + { + HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("xp") + .addPathSegment("update") + .addQueryParameter("username", username) + .build(); + + Request request = new Request.Builder() + .url(url) + .build(); + + RuneLiteAPI.CLIENT.newCall(request).execute().close(); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/xp/XpData.java b/http-api/src/main/java/net/runelite/http/api/xp/XpData.java new file mode 100644 index 0000000000..33217bafe0 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/xp/XpData.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.xp; + +import java.time.Instant; +import lombok.Data; + +@Data +public class XpData +{ + private Instant time; + + private int attack_xp; + private int defence_xp; + private int strength_xp; + private int hitpoints_xp; + private int ranged_xp; + private int prayer_xp; + private int magic_xp; + private int cooking_xp; + private int woodcutting_xp; + private int fletching_xp; + private int fishing_xp; + private int firemaking_xp; + private int crafting_xp; + private int smithing_xp; + private int mining_xp; + private int herblore_xp; + private int agility_xp; + private int thieving_xp; + private int slayer_xp; + private int farming_xp; + private int runecraft_xp; + private int hunter_xp; + private int construction_xp; + + private int attack_rank; + private int defence_rank; + private int strength_rank; + private int hitpoints_rank; + private int ranged_rank; + private int prayer_rank; + private int magic_rank; + private int cooking_rank; + private int woodcutting_rank; + private int fletching_rank; + private int fishing_rank; + private int firemaking_rank; + private int crafting_rank; + private int smithing_rank; + private int mining_rank; + private int herblore_rank; + private int agility_rank; + private int thieving_rank; + private int slayer_rank; + private int farming_rank; + private int runecraft_rank; + private int hunter_rank; + private int construction_rank; +} diff --git a/http-service/pom.xml b/http-service/pom.xml index b24e62999a..c7d52c1a51 100644 --- a/http-service/pom.xml +++ b/http-service/pom.xml @@ -38,6 +38,7 @@ 1.5.6.RELEASE 1.16.18 + 1.2.0.Final @@ -64,6 +65,11 @@ true + + org.mapstruct + mapstruct-jdk8 + ${mapstruct.version} + org.projectlombok lombok @@ -141,6 +147,25 @@ runelite-${project.version} + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok + ${lombok.version} + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java index 319ecaee9c..f04d4a4a54 100644 --- a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java +++ b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java @@ -31,7 +31,6 @@ import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; - import net.runelite.http.service.util.InstantConverter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -69,6 +68,15 @@ public class SpringBootWebApplication extends SpringBootServletInitializer return new Sql2o(dataSource, new NoQuirks(converters)); } + @Bean("Runelite XP Tracker SQL2O") + Sql2o trackerSql2o() throws NamingException + { + DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite-tracker"); + Map converters = new HashMap<>(); + converters.put(Instant.class, new InstantConverter()); + return new Sql2o(dataSource, new NoQuirks(converters)); + } + @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { diff --git a/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreController.java b/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreController.java index faccbf06a3..9b581dcd87 100644 --- a/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreController.java +++ b/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreController.java @@ -31,6 +31,7 @@ import net.runelite.http.api.hiscore.HiscoreSkill; import net.runelite.http.api.hiscore.SingleHiscoreSkillResult; import net.runelite.http.api.hiscore.Skill; import net.runelite.http.service.util.HiscoreEndpointEditor; +import net.runelite.http.service.xp.XpTrackerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; @@ -46,12 +47,25 @@ public class HiscoreController @Autowired private HiscoreService hiscoreService; + @Autowired + private XpTrackerService xpTrackerService; + @RequestMapping("/{endpoint}") public HiscoreResult lookup(@PathVariable HiscoreEndpoint endpoint, @RequestParam String username) throws IOException { HiscoreResultBuilder resultBuilder = hiscoreService.lookupUsername(username, endpoint); HiscoreResult result = resultBuilder.build(); + // Submit to xp tracker? + switch (endpoint) + { + case NORMAL: + case IRONMAN: + case ULTIMATE_IRONMAN: + case HARDCORE_IRONMAN: + xpTrackerService.update(username, result); + } + return result; } diff --git a/http-service/src/main/java/net/runelite/http/service/xp/XpMapper.java b/http-service/src/main/java/net/runelite/http/service/xp/XpMapper.java new file mode 100644 index 0000000000..0e23aa3959 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xp/XpMapper.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xp; + +import net.runelite.http.api.xp.XpData; +import net.runelite.http.service.xp.beans.XpEntity; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface XpMapper +{ + XpMapper INSTANCE = Mappers.getMapper(XpMapper.class); + + XpData xpEntityToXpData(XpEntity xpEntity); +} diff --git a/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerController.java b/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerController.java new file mode 100644 index 0000000000..7606d4e4ee --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerController.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xp; + +import java.io.IOException; +import java.time.Instant; +import net.runelite.http.api.xp.XpData; +import net.runelite.http.service.xp.beans.XpEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/xp") +public class XpTrackerController +{ + @Autowired + private XpTrackerService xpTrackerService; + + @RequestMapping("/update") + public void update(@RequestParam String username) throws IOException + { + xpTrackerService.update(username); + } + + @RequestMapping("/get") + public XpData get(@RequestParam String username, @RequestParam(required = false) Instant time) throws IOException + { + if (time == null) + { + time = Instant.now(); + } + XpEntity xpEntity = xpTrackerService.findXpAtTime(username, time); + return XpMapper.INSTANCE.xpEntityToXpData(xpEntity); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerService.java b/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerService.java new file mode 100644 index 0000000000..7e79f1de80 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerService.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xp; + +import java.io.IOException; +import java.time.Instant; +import net.runelite.http.api.hiscore.HiscoreEndpoint; +import net.runelite.http.api.hiscore.HiscoreResult; +import net.runelite.http.service.hiscore.HiscoreResultBuilder; +import net.runelite.http.service.hiscore.HiscoreService; +import net.runelite.http.service.xp.beans.PlayerEntity; +import net.runelite.http.service.xp.beans.XpEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Sql2o; + +@Service +public class XpTrackerService +{ + @Autowired + @Qualifier("Runelite XP Tracker SQL2O") + private Sql2o sql2o; + + @Autowired + private HiscoreService hiscoreService; + + public void update(String username) throws IOException + { + HiscoreResultBuilder hiscoreResultBuilder = hiscoreService.lookupUsername(username, HiscoreEndpoint.NORMAL); + HiscoreResult hiscoreResult = hiscoreResultBuilder.build(); + update(username, hiscoreResult); + } + + public void update(String username, HiscoreResult hiscoreResult) + { + try (Connection con = sql2o.open()) + { + PlayerEntity playerEntity = findOrCreatePlayer(username); + + con.createQuery("insert into xp (player,attack_xp,defence_xp,strength_xp,hitpoints_xp,ranged_xp,prayer_xp,magic_xp,cooking_xp,woodcutting_xp," + + "fletching_xp,fishing_xp,firemaking_xp,crafting_xp,smithing_xp,mining_xp,herblore_xp,agility_xp,thieving_xp,slayer_xp,farming_xp," + + "runecraft_xp,hunter_xp,construction_xp,attack_rank,defence_rank,strength_rank,hitpoints_rank,ranged_rank,prayer_rank,magic_rank," + + "cooking_rank,woodcutting_rank,fletching_rank,fishing_rank,firemaking_rank,crafting_rank,smithing_rank,mining_rank,herblore_rank," + + "agility_rank,thieving_rank,slayer_rank,farming_rank,runecraft_rank,hunter_rank,construction_rank,overall_rank) values (:player,:attack_xp,:defence_xp," + + ":strength_xp,:hitpoints_xp,:ranged_xp,:prayer_xp,:magic_xp,:cooking_xp,:woodcutting_xp,:fletching_xp,:fishing_xp,:firemaking_xp," + + ":crafting_xp,:smithing_xp,:mining_xp,:herblore_xp,:agility_xp,:thieving_xp,:slayer_xp,:farming_xp,:runecraft_xp,:hunter_xp," + + ":construction_xp,:attack_rank,:defence_rank,:strength_rank,:hitpoints_rank,:ranged_rank,:prayer_rank,:magic_rank,:cooking_rank," + + ":woodcutting_rank,:fletching_rank,:fishing_rank,:firemaking_rank,:crafting_rank,:smithing_rank,:mining_rank,:herblore_rank," + + ":agility_rank,:thieving_rank,:slayer_rank,:farming_rank,:runecraft_rank,:hunter_rank,:construction_rank,:overall_rank)") + .addParameter("player", playerEntity.getId()) + .addParameter("attack_xp", hiscoreResult.getAttack().getExperience()) + .addParameter("defence_xp", hiscoreResult.getDefence().getExperience()) + .addParameter("strength_xp", hiscoreResult.getStrength().getExperience()) + .addParameter("hitpoints_xp", hiscoreResult.getHitpoints().getExperience()) + .addParameter("ranged_xp", hiscoreResult.getRanged().getExperience()) + .addParameter("prayer_xp", hiscoreResult.getPrayer().getExperience()) + .addParameter("magic_xp", hiscoreResult.getMagic().getExperience()) + .addParameter("cooking_xp", hiscoreResult.getCooking().getExperience()) + .addParameter("woodcutting_xp", hiscoreResult.getWoodcutting().getExperience()) + .addParameter("fletching_xp", hiscoreResult.getFletching().getExperience()) + .addParameter("fishing_xp", hiscoreResult.getFishing().getExperience()) + .addParameter("firemaking_xp", hiscoreResult.getFiremaking().getExperience()) + .addParameter("crafting_xp", hiscoreResult.getCrafting().getExperience()) + .addParameter("smithing_xp", hiscoreResult.getSmithing().getExperience()) + .addParameter("mining_xp", hiscoreResult.getMining().getExperience()) + .addParameter("herblore_xp", hiscoreResult.getHerblore().getExperience()) + .addParameter("agility_xp", hiscoreResult.getAgility().getExperience()) + .addParameter("thieving_xp", hiscoreResult.getThieving().getExperience()) + .addParameter("slayer_xp", hiscoreResult.getSlayer().getExperience()) + .addParameter("farming_xp", hiscoreResult.getFarming().getExperience()) + .addParameter("runecraft_xp", hiscoreResult.getRunecraft().getExperience()) + .addParameter("hunter_xp", hiscoreResult.getHunter().getExperience()) + .addParameter("construction_xp", hiscoreResult.getConstruction().getExperience()) + .addParameter("attack_rank", hiscoreResult.getAttack().getRank()) + .addParameter("defence_rank", hiscoreResult.getDefence().getRank()) + .addParameter("strength_rank", hiscoreResult.getStrength().getRank()) + .addParameter("hitpoints_rank", hiscoreResult.getHitpoints().getRank()) + .addParameter("ranged_rank", hiscoreResult.getRanged().getRank()) + .addParameter("prayer_rank", hiscoreResult.getPrayer().getRank()) + .addParameter("magic_rank", hiscoreResult.getMagic().getRank()) + .addParameter("cooking_rank", hiscoreResult.getCooking().getRank()) + .addParameter("woodcutting_rank", hiscoreResult.getWoodcutting().getRank()) + .addParameter("fletching_rank", hiscoreResult.getFletching().getRank()) + .addParameter("fishing_rank", hiscoreResult.getFishing().getRank()) + .addParameter("firemaking_rank", hiscoreResult.getFiremaking().getRank()) + .addParameter("crafting_rank", hiscoreResult.getCrafting().getRank()) + .addParameter("smithing_rank", hiscoreResult.getSmithing().getRank()) + .addParameter("mining_rank", hiscoreResult.getMining().getRank()) + .addParameter("herblore_rank", hiscoreResult.getHerblore().getRank()) + .addParameter("agility_rank", hiscoreResult.getAgility().getRank()) + .addParameter("thieving_rank", hiscoreResult.getThieving().getRank()) + .addParameter("slayer_rank", hiscoreResult.getSlayer().getRank()) + .addParameter("farming_rank", hiscoreResult.getFarming().getRank()) + .addParameter("runecraft_rank", hiscoreResult.getRunecraft().getRank()) + .addParameter("hunter_rank", hiscoreResult.getHunter().getRank()) + .addParameter("construction_rank", hiscoreResult.getConstruction().getRank()) + .addParameter("overall_rank", hiscoreResult.getOverall().getRank()) + .executeUpdate(); + } + } + + private synchronized PlayerEntity findOrCreatePlayer(String username) + { + try (Connection con = sql2o.open()) + { + PlayerEntity playerEntity = con.createQuery("select * from player where name = :name") + .addParameter("name", username) + .executeAndFetchFirst(PlayerEntity.class); + if (playerEntity != null) + { + return playerEntity; + } + + Instant now = Instant.now(); + + int id = con.createQuery("insert into player (name, tracked_since) values (:name, :tracked_since)") + .addParameter("name", username) + .addParameter("tracked_since", now) + .executeUpdate() + .getKey(int.class); + + playerEntity = new PlayerEntity(); + playerEntity.setId(id); + playerEntity.setName(username); + playerEntity.setTracked_since(now); + return playerEntity; + } + } + + public XpEntity findXpAtTime(String username, Instant time) + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select * from xp join player on player.id=xp.player where player.name = :username and time <= :time order by time desc limit 1") + .throwOnMappingFailure(false) + .addParameter("username", username) + .addParameter("time", time) + .executeAndFetchFirst(XpEntity.class); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/xp/beans/PlayerEntity.java b/http-service/src/main/java/net/runelite/http/service/xp/beans/PlayerEntity.java new file mode 100644 index 0000000000..d07d1d4640 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xp/beans/PlayerEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xp.beans; + +import java.time.Instant; +import lombok.Data; + +@Data +public class PlayerEntity +{ + private Integer id; + private String name; + private Instant tracked_since; +} diff --git a/http-service/src/main/java/net/runelite/http/service/xp/beans/XpEntity.java b/http-service/src/main/java/net/runelite/http/service/xp/beans/XpEntity.java new file mode 100644 index 0000000000..acab775873 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/xp/beans/XpEntity.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xp.beans; + +import java.time.Instant; +import lombok.Data; + +@Data +public class XpEntity +{ + private Integer id; + private Instant time; + private Integer player; + private int overall_xp; + private int attack_xp; + private int defence_xp; + private int strength_xp; + private int hitpoints_xp; + private int ranged_xp; + private int prayer_xp; + private int magic_xp; + private int cooking_xp; + private int woodcutting_xp; + private int fletching_xp; + private int fishing_xp; + private int firemaking_xp; + private int crafting_xp; + private int smithing_xp; + private int mining_xp; + private int herblore_xp; + private int agility_xp; + private int thieving_xp; + private int slayer_xp; + private int farming_xp; + private int runecraft_xp; + private int hunter_xp; + private int construction_xp; + + private int overall_rank; + private int attack_rank; + private int defence_rank; + private int strength_rank; + private int hitpoints_rank; + private int ranged_rank; + private int prayer_rank; + private int magic_rank; + private int cooking_rank; + private int woodcutting_rank; + private int fletching_rank; + private int fishing_rank; + private int firemaking_rank; + private int crafting_rank; + private int smithing_rank; + private int mining_rank; + private int herblore_rank; + private int agility_rank; + private int thieving_rank; + private int slayer_rank; + private int farming_rank; + private int runecraft_rank; + private int hunter_rank; + private int construction_rank; +} diff --git a/http-service/src/main/resources/net/runelite/http/service/xp/schema.sql b/http-service/src/main/resources/net/runelite/http/service/xp/schema.sql new file mode 100644 index 0000000000..80465fd48a --- /dev/null +++ b/http-service/src/main/resources/net/runelite/http/service/xp/schema.sql @@ -0,0 +1,132 @@ +-- MySQL dump 10.16 Distrib 10.2.9-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: xptracker +-- ------------------------------------------------------ +-- Server version 10.2.9-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `player` +-- + +DROP TABLE IF EXISTS `player`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `player` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(32) NOT NULL, + `tracked_since` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `xp` +-- + +DROP TABLE IF EXISTS `xp`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `xp` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `time` timestamp NOT NULL DEFAULT current_timestamp(), + `player` int(11) NOT NULL, + `attack_xp` int(11) NOT NULL, + `defence_xp` int(11) NOT NULL, + `strength_xp` int(11) NOT NULL, + `hitpoints_xp` int(11) NOT NULL, + `ranged_xp` int(11) NOT NULL, + `prayer_xp` int(11) NOT NULL, + `magic_xp` int(11) NOT NULL, + `cooking_xp` int(11) NOT NULL, + `woodcutting_xp` int(11) NOT NULL, + `fletching_xp` int(11) NOT NULL, + `fishing_xp` int(11) NOT NULL, + `firemaking_xp` int(11) NOT NULL, + `crafting_xp` int(11) NOT NULL, + `smithing_xp` int(11) NOT NULL, + `mining_xp` int(11) NOT NULL, + `herblore_xp` int(11) NOT NULL, + `agility_xp` int(11) NOT NULL, + `thieving_xp` int(11) NOT NULL, + `slayer_xp` int(11) NOT NULL, + `farming_xp` int(11) NOT NULL, + `runecraft_xp` int(11) NOT NULL, + `hunter_xp` int(11) NOT NULL, + `construction_xp` int(11) NOT NULL, + `overall_xp` int(11) GENERATED ALWAYS AS (`attack_xp` + `defence_xp` + `strength_xp` + `hitpoints_xp` + `ranged_xp` + `prayer_xp` + `magic_xp` + `cooking_xp` + `woodcutting_xp` + `fletching_xp` + `fishing_xp` + `firemaking_xp` + `crafting_xp` + `smithing_xp` + `mining_xp` + `herblore_xp` + `agility_xp` + `thieving_xp` + `slayer_xp` + `farming_xp` + `runecraft_xp` + `hunter_xp` + `construction_xp`) VIRTUAL, + `attack_level` int(11) GENERATED ALWAYS AS (level_for_xp(`attack_xp` AS `attack_xp`)) VIRTUAL, + `defence_level` int(11) GENERATED ALWAYS AS (level_for_xp(`defence_xp` AS `defence_xp`)) VIRTUAL, + `strength_level` int(11) GENERATED ALWAYS AS (level_for_xp(`strength_xp` AS `strength_xp`)) VIRTUAL, + `hitpoints_level` int(11) GENERATED ALWAYS AS (level_for_xp(`hitpoints_xp` AS `hitpoints_xp`)) VIRTUAL, + `ranged_level` int(11) GENERATED ALWAYS AS (level_for_xp(`ranged_xp` AS `ranged_xp`)) VIRTUAL, + `prayer_level` int(11) GENERATED ALWAYS AS (level_for_xp(`prayer_xp` AS `prayer_xp`)) VIRTUAL, + `magic_level` int(11) GENERATED ALWAYS AS (level_for_xp(`magic_xp` AS `magic_xp`)) VIRTUAL, + `cooking_level` int(11) GENERATED ALWAYS AS (level_for_xp(`cooking_xp` AS `cooking_xp`)) VIRTUAL, + `woodcutting_level` int(11) GENERATED ALWAYS AS (level_for_xp(`woodcutting_xp` AS `woodcutting_xp`)) VIRTUAL, + `fletching_level` int(11) GENERATED ALWAYS AS (level_for_xp(`fletching_xp` AS `fletching_xp`)) VIRTUAL, + `fishing_level` int(11) GENERATED ALWAYS AS (level_for_xp(`fishing_xp` AS `fishing_xp`)) VIRTUAL, + `firemaking_level` int(11) GENERATED ALWAYS AS (level_for_xp(`firemaking_xp` AS `firemaking_xp`)) VIRTUAL, + `crafting_level` int(11) GENERATED ALWAYS AS (level_for_xp(`crafting_xp` AS `crafting_xp`)) VIRTUAL, + `smithing_level` int(11) GENERATED ALWAYS AS (level_for_xp(`smithing_xp` AS `smithing_xp`)) VIRTUAL, + `mining_level` int(11) GENERATED ALWAYS AS (level_for_xp(`mining_xp` AS `mining_xp`)) VIRTUAL, + `herblore_level` int(11) GENERATED ALWAYS AS (level_for_xp(`herblore_xp` AS `herblore_xp`)) VIRTUAL, + `agility_level` int(11) GENERATED ALWAYS AS (level_for_xp(`agility_xp` AS `agility_xp`)) VIRTUAL, + `thieving_level` int(11) GENERATED ALWAYS AS (level_for_xp(`thieving_xp` AS `thieving_xp`)) VIRTUAL, + `slayer_level` int(11) GENERATED ALWAYS AS (level_for_xp(`slayer_xp` AS `slayer_xp`)) VIRTUAL, + `farming_level` int(11) GENERATED ALWAYS AS (level_for_xp(`farming_xp` AS `farming_xp`)) VIRTUAL, + `runecraft_level` int(11) GENERATED ALWAYS AS (level_for_xp(`runecraft_xp` AS `runecraft_xp`)) VIRTUAL, + `hunter_level` int(11) GENERATED ALWAYS AS (level_for_xp(`hunter_xp` AS `hunter_xp`)) VIRTUAL, + `construction_level` int(11) GENERATED ALWAYS AS (level_for_xp(`construction_xp` AS `construction_xp`)) VIRTUAL, + `overall_level` int(11) GENERATED ALWAYS AS (`attack_level` + `defence_level` + `strength_level` + `hitpoints_level` + `ranged_level` + `prayer_level` + `magic_level` + `cooking_level` + `woodcutting_level` + `fletching_level` + `fishing_level` + `firemaking_level` + `crafting_level` + `smithing_level` + `mining_level` + `herblore_level` + `agility_level` + `thieving_level` + `slayer_level` + `farming_level` + `runecraft_level` + `hunter_level` + `construction_level`) VIRTUAL, + `attack_rank` int(11) NOT NULL, + `defence_rank` int(11) NOT NULL, + `strength_rank` int(11) NOT NULL, + `hitpoints_rank` int(11) NOT NULL, + `ranged_rank` int(11) NOT NULL, + `prayer_rank` int(11) NOT NULL, + `magic_rank` int(11) NOT NULL, + `cooking_rank` int(11) NOT NULL, + `woodcutting_rank` int(11) NOT NULL, + `fletching_rank` int(11) NOT NULL, + `fishing_rank` int(11) NOT NULL, + `firemaking_rank` int(11) NOT NULL, + `crafting_rank` int(11) NOT NULL, + `smithing_rank` int(11) NOT NULL, + `mining_rank` int(11) NOT NULL, + `herblore_rank` int(11) NOT NULL, + `agility_rank` int(11) NOT NULL, + `thieving_rank` int(11) NOT NULL, + `slayer_rank` int(11) NOT NULL, + `farming_rank` int(11) NOT NULL, + `runecraft_rank` int(11) NOT NULL, + `hunter_rank` int(11) NOT NULL, + `construction_rank` int(11) NOT NULL, + `overall_rank` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `player_time` (`player`,`time`), + CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2018-01-20 18:37:09 diff --git a/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java b/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java index 45e5ed35f3..0edc296898 100644 --- a/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java +++ b/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java @@ -28,7 +28,6 @@ import java.time.Instant; import java.util.HashMap; import java.util.Map; import javax.naming.NamingException; - import net.runelite.http.service.util.InstantConverter; import org.junit.Ignore; import org.junit.Test; @@ -47,7 +46,7 @@ public class SpringBootWebApplicationTest { Map converters = new HashMap<>(); converters.put(Instant.class, new InstantConverter()); - return new Sql2o("jdbc:mysql://localhost/runelite", "root", "", new NoQuirks(converters)); + return new Sql2o("jdbc:mysql://192.168.1.2/runelite", "runelite", "runelite", new NoQuirks(converters)); } @Bean("Runelite Cache SQL2O") @@ -55,7 +54,15 @@ public class SpringBootWebApplicationTest { Map converters = new HashMap<>(); converters.put(Instant.class, new InstantConverter()); - return new Sql2o("jdbc:mysql://localhost/cache", "root", "", new NoQuirks(converters)); + return new Sql2o("jdbc:mysql://192.168.1.2/cache", "runelite", "runelite", new NoQuirks(converters)); + } + + @Bean("Runelite XP Tracker SQL2O") + Sql2o xpSql2o() throws NamingException + { + Map converters = new HashMap<>(); + converters.put(Instant.class, new InstantConverter()); + return new Sql2o("jdbc:mysql://192.168.1.2/xptracker", "runelite", "runelite", new NoQuirks(converters)); } @Test