Initial commit
This commit is contained in:
48
docker-compose.yml
Normal file
48
docker-compose.yml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
hostname: mongo
|
||||||
|
image: mongo:latest
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- mongo_data:/data/db
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: root
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: password
|
||||||
|
|
||||||
|
mongo-express:
|
||||||
|
image: mongo-express
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 8081:8081
|
||||||
|
environment:
|
||||||
|
ME_CONFIG_MONGODB_ADMINUSERNAME: root
|
||||||
|
ME_CONFIG_MONGODB_ADMINPASSWORD: password
|
||||||
|
ME_CONFIG_MONGODB_URL: mongodb://root:password@mongo:27017/
|
||||||
|
ME_CONFIG_BASICAUTH: false
|
||||||
|
|
||||||
|
patch_detector:
|
||||||
|
build: ./patch_detector
|
||||||
|
restart: no
|
||||||
|
environment:
|
||||||
|
MONGO_USER: root
|
||||||
|
MONGO_PASS: password
|
||||||
|
|
||||||
|
match_collector:
|
||||||
|
build: ./match_collector
|
||||||
|
restart: no
|
||||||
|
environment:
|
||||||
|
MONGO_USER: root
|
||||||
|
MONGO_PASS: password
|
||||||
|
RIOT_API_KEY: RGAPI-f3f73fe9-d1e7-4672-9b66-a41c9e92b1f1
|
||||||
|
# restarter:
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build: ./frontend
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
environment:
|
||||||
|
MONGO_USER: root
|
||||||
|
MONGO_PASS: password
|
||||||
|
volumes:
|
||||||
|
mongo_data:
|
||||||
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
12
frontend/Dockerfile
Normal file
12
frontend/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM node:lts-alpine as base
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM base as build
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM base
|
||||||
|
COPY --from=build /app/.output /app/.output
|
||||||
|
CMD ["node", ".output/server/index.mjs"]
|
||||||
75
frontend/README.md
Normal file
75
frontend/README.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Nuxt Minimal Starter
|
||||||
|
|
||||||
|
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on `http://localhost:3000`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm preview
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn preview
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||||
9
frontend/app.vue
Normal file
9
frontend/app.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtRouteAnnouncer />
|
||||||
|
<!-- <NuxtWelcome /> -->
|
||||||
|
<NuxtLayout>
|
||||||
|
<NuxtPage />
|
||||||
|
</NuxtLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
40
frontend/assets/css/main.css
Normal file
40
frontend/assets/css/main.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-surface: #312E2C;
|
||||||
|
--color-on-surface: #B7B8E1;
|
||||||
|
--color-surface-darker: #1f1d1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Font setting */
|
||||||
|
h1,h2,h3,h4,h5,h6,p,a,input[type=text] {
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default margins to none */
|
||||||
|
h1,h2,h3,h4,h5,h6,p,a {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Different title settings */
|
||||||
|
h1 {
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
50
frontend/components/ChampionSelector.vue
Normal file
50
frontend/components/ChampionSelector.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup>
|
||||||
|
const {data: champions} = await useFetch(CDRAGON_BASE + "plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json")
|
||||||
|
const filteredChampions = ref(champions.value.slice(1))
|
||||||
|
|
||||||
|
const searchBar = ref(null)
|
||||||
|
watch(searchBar, (newS, oldS) => {searchBar.value.focus()})
|
||||||
|
const searchText = ref("")
|
||||||
|
watch(searchText, (newT, oldT) => {
|
||||||
|
filteredChampions.value = champions.value.slice(1).filter((champion) => champion.name.toLowerCase().includes(searchText.value.toLowerCase()))
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div style="width: fit-content; margin: auto;">
|
||||||
|
<input v-model="searchText" ref="searchBar" class="search-bar" type="text"/>
|
||||||
|
</div>
|
||||||
|
<div class="champion-container" style="margin-top: 20px;">
|
||||||
|
<RouterLink style="margin-left: 5px; margin-right: 5px;" v-for="champion in filteredChampions" :to="'/champion/' + champion.id">
|
||||||
|
<img :src="CDRAGON_BASE + mapPath(champion.squarePortraitPath)" :alt="champion.name"/>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.search-bar {
|
||||||
|
width: 400px;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
background-color: var(--color-surface-darker);
|
||||||
|
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.search-bar:focus {
|
||||||
|
border: 2px solid var(--color-on-surface);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.champion-container {
|
||||||
|
width: 1385px;
|
||||||
|
height: 660px;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
46
frontend/components/ChampionTitle.vue
Normal file
46
frontend/components/ChampionTitle.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
championId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
winrate: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
pickrate: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
gameCount: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const championId = Number(props.championId)
|
||||||
|
|
||||||
|
const winrate = (props.winrate * 100).toFixed(2)
|
||||||
|
const pickrate = (props.pickrate * 100).toFixed(2)
|
||||||
|
const gameCount = props.gameCount
|
||||||
|
|
||||||
|
const { data: championData } = await useFetch(CDRAGON_BASE + "plugins/rcp-be-lol-game-data/global/default/v1/champions/" + championId + ".json")
|
||||||
|
const championName = championData.value.name
|
||||||
|
const championDescription = championData.value.title
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="display: flex;">
|
||||||
|
<img width="160" height="160" :src="CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/' + championId + '.png'"/>
|
||||||
|
<div style="margin-left: 15px; margin-top: 5px;">
|
||||||
|
<h1>{{ championName }}</h1>
|
||||||
|
<h3 style="margin-top: 5px">{{ championDescription }}</h3>
|
||||||
|
<div style="margin-top: 30px; display: flex;">
|
||||||
|
<h2>{{ winrate }}% win.</h2>
|
||||||
|
<h2 style="margin-left: 20px; margin-right: 20px;">{{ pickrate }}% pick.</h2>
|
||||||
|
<h2>{{ gameCount }} games</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
107
frontend/components/RunePage.vue
Normal file
107
frontend/components/RunePage.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
// Runes styles: domination, precision, sorcery, inspiration, resolve
|
||||||
|
primaryStyleId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secondaryStyleId: {
|
||||||
|
type:String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selectionIds: {
|
||||||
|
type:Array,
|
||||||
|
required: false,
|
||||||
|
default: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const primaryStyle = ref({slots:[]})
|
||||||
|
const secondaryStyle = ref({slots:[]})
|
||||||
|
|
||||||
|
let { data: perks_data } = await useFetch("https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/perks.json")
|
||||||
|
const perks = reactive(new Map())
|
||||||
|
for(let perk of perks_data.value) {
|
||||||
|
perks.set(perk.id, perk)
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data: stylesData } = await useFetch("https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json")
|
||||||
|
watch(() => props.primaryStyleId, async (newP, oldP) => {refreshStyles()})
|
||||||
|
watch(() => props.secondaryStyleId, async (newP, oldP) => {refreshStyles()})
|
||||||
|
|
||||||
|
function refreshStyles() {
|
||||||
|
for(let style of stylesData.value.styles) {
|
||||||
|
if(style.id == props.primaryStyleId) {
|
||||||
|
primaryStyle.value = style
|
||||||
|
}
|
||||||
|
if(style.id == props.secondaryStyleId) {
|
||||||
|
secondaryStyle.value = style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshStyles()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="display: flex;">
|
||||||
|
<div class="rune-holder">
|
||||||
|
<div class="rune-slot"><img style="margin: auto;" :src="CDRAGON_BASE + mapPath(primaryStyle.iconPath)" /></div>
|
||||||
|
<div class="rune-slot" v-for="slot in primaryStyle.slots.slice(0, 1)">
|
||||||
|
<img width="48" v-for="perk in slot.perks" :class="'rune-img rune-keystone ' + (props.selectionIds.includes(perk) ? 'rune-activated' : '')" :src="'https://raw.communitydragon.org/latest/' + mapPath(perks.get(perk).iconPath)"/>
|
||||||
|
</div>
|
||||||
|
<div class="rune-slot" v-for="slot in primaryStyle.slots.slice(1, 4)">
|
||||||
|
<img width="48" v-for="perk in slot.perks" :class="'rune-img ' + (props.selectionIds.includes(perk) ? 'rune-activated' : '')" :src="'https://raw.communitydragon.org/latest/' + mapPath(perks.get(perk).iconPath)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="rune-spacer-bar"></div>
|
||||||
|
<div class="rune-holder" style="align-content: end">
|
||||||
|
<div class="rune-slot"><img style="margin: auto;" :src="CDRAGON_BASE + mapPath(secondaryStyle.iconPath)" /></div>
|
||||||
|
<div class="rune-slot" v-for="slot in secondaryStyle.slots.slice(1, 4)">
|
||||||
|
<img width="48" v-for="perk in slot.perks" :class="'rune-img ' + (props.selectionIds.includes(perk) ? 'rune-activated' : '')" :src="'https://raw.communitydragon.org/latest/' + mapPath(perks.get(perk).iconPath)"/>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="rune-slot mini" v-for="slot in primaryStyle.slots.slice(4, 7)">
|
||||||
|
<img width="32" v-for="perk in slot.perks" :class="'rune-img ' + (props.selectionIds.includes(perk) ? 'rune-activated' : '')" :src="'https://raw.communitydragon.org/latest/' + mapPath(perks.get(perk).iconPath)"/>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.rune-holder {
|
||||||
|
/* align-content: end; */
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.rune-slot {
|
||||||
|
width: calc(48*3px + 60px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
.mini {
|
||||||
|
margin: auto;
|
||||||
|
width: calc(32*3px + 60px);
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.rune-img {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
filter: grayscale(1);
|
||||||
|
border: 1px var(--color-on-surface) solid;
|
||||||
|
border-radius:50%;
|
||||||
|
}
|
||||||
|
.rune-keystone {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.rune-activated {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
.rune-spacer-bar {
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-right: 20px;
|
||||||
|
border: 1px var(--color-on-surface) solid;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
82
frontend/components/RuneSelector.vue
Normal file
82
frontend/components/RuneSelector.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
// Runes styles: domination, precision, sorcery, inspiration, resolve
|
||||||
|
runes: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const runes = props.runes
|
||||||
|
|
||||||
|
const currentlySelectedPage = ref(runes[0])
|
||||||
|
const primaryStyles = ref([])
|
||||||
|
const secondaryStyles = ref([])
|
||||||
|
const keystoneIds = ref([])
|
||||||
|
|
||||||
|
let { data: perks_data } = await useFetch(CDRAGON_BASE + "plugins/rcp-be-lol-game-data/global/default/v1/perks.json")
|
||||||
|
const perks = reactive(new Map())
|
||||||
|
for(let perk of perks_data.value) {
|
||||||
|
perks.set(perk.id, perk)
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data: stylesData } = await useFetch(CDRAGON_BASE + "plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json")
|
||||||
|
for(let style of stylesData.value.styles) {
|
||||||
|
for(let rune of runes) {
|
||||||
|
if(style.id == rune.primaryStyle) {
|
||||||
|
rune.primaryStyleValue = style
|
||||||
|
primaryStyles.value.push(style)
|
||||||
|
for(let perk of style.slots[0].perks) {
|
||||||
|
if(rune.selections.includes(perk)) {
|
||||||
|
rune.keystoneValue = perk
|
||||||
|
keystoneIds.value.push(perk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(style.id == rune.secondaryStyle) {
|
||||||
|
secondaryStyles.value.push(style)
|
||||||
|
rune.secondaryStyleValue = style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runeSelect(rune) {
|
||||||
|
currentlySelectedPage.value = rune
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="width: fit-content;">
|
||||||
|
<RunePage style="margin:auto; width: fit-content;" :primaryStyleId="currentlySelectedPage.primaryStyle" :secondaryStyleId="currentlySelectedPage.secondaryStyle" :selectionIds="currentlySelectedPage.selections" />
|
||||||
|
<div style="display: flex; margin-top: 20px;">
|
||||||
|
<div v-for="rune in runes" :class="'rune-selector-entry ' + (rune == currentlySelectedPage ? 'rune-selector-entry-selected' : '')" @click="runeSelect(rune)">
|
||||||
|
<div style="display: flex; margin-top: 20px;">
|
||||||
|
<img style="margin: auto;" :src="CDRAGON_BASE + mapPath(rune.primaryStyleValue.iconPath)" />
|
||||||
|
<img width="34" :src="CDRAGON_BASE + ( mapPath(perks.get(rune.keystoneValue).iconPath))"/>
|
||||||
|
<img style="margin: auto;" :src="CDRAGON_BASE + mapPath(rune.secondaryStyleValue.iconPath)" />
|
||||||
|
</div>
|
||||||
|
<h3 style="text-align: center; margin-top: 10px;">{{ (rune.pickrate * 100).toFixed(2) }}% pick.</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.rune-selector-entry {
|
||||||
|
width: 200px;
|
||||||
|
height: 120px;
|
||||||
|
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
border-radius: 8%;
|
||||||
|
border: 1px solid var(--color-on-surface);
|
||||||
|
}
|
||||||
|
.rune-selector-entry:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.rune-selector-entry-selected {
|
||||||
|
background-color: var(--color-surface-darker);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
10
frontend/nuxt.config.ts
Normal file
10
frontend/nuxt.config.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
compatibilityDate: '2024-04-03',
|
||||||
|
devtools: { enabled: true },
|
||||||
|
css: ['~/assets/css/main.css'],
|
||||||
|
routeRules: {
|
||||||
|
'/' : {prerender: true},
|
||||||
|
'/champion/**' : {swr: true}
|
||||||
|
}
|
||||||
|
})
|
||||||
9690
frontend/package-lock.json
generated
Normal file
9690
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
frontend/package.json
Normal file
18
frontend/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "nuxt-app",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mongodb": "^6.10.0",
|
||||||
|
"nuxt": "^3.14.1592",
|
||||||
|
"vue": "latest",
|
||||||
|
"vue-router": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
frontend/pages/champion/[id].vue
Normal file
15
frontend/pages/champion/[id].vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup>
|
||||||
|
const route = useRoute()
|
||||||
|
const championId = route.params.id
|
||||||
|
|
||||||
|
const { data : championData } = await useFetch("/api/champion/" + championId)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ChampionTitle style="margin-top: 64px; margin-left: 64px;" :champion-id="championId" :winrate="championData.winrate" :pickrate="championData.pickrate" :game-count="championData.gameCount" />
|
||||||
|
<!-- <RunePage style="margin-top: 64px; margin-left: 64px;" primaryStyleId="8000" secondaryStyleId="8300" :selectionIds="selections" /> -->
|
||||||
|
<RuneSelector style="margin-top: 20px; margin-left: 64px;" :runes="championData.runes" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
6
frontend/pages/index.vue
Normal file
6
frontend/pages/index.vue
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ChampionSelector style="margin-top: 64px;"/>
|
||||||
|
</template>
|
||||||
BIN
frontend/public/favicon.ico
Normal file
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
1
frontend/public/robots.txt
Normal file
1
frontend/public/robots.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
30
frontend/server/api/champion/[id].js
Normal file
30
frontend/server/api/champion/[id].js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { MongoClient } from 'mongodb'
|
||||||
|
|
||||||
|
async function connectToDatabase() {
|
||||||
|
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
||||||
|
const client = new MongoClient(`mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@mongo:27017`)
|
||||||
|
await client.connect()
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
async function fetchLatestPatch(client) {
|
||||||
|
const database = client.db("patches");
|
||||||
|
const patches = database.collection("patches");
|
||||||
|
const latestPatch = await patches.find().limit(1).sort({date:-1}).next()
|
||||||
|
return latestPatch.patch
|
||||||
|
}
|
||||||
|
async function championInfos(client, patch, championId) {
|
||||||
|
const database = client.db("champions");
|
||||||
|
const collection = database.collection(patch);
|
||||||
|
const query = { id:Number(championId) };
|
||||||
|
const championInfo = await collection.findOne(query);
|
||||||
|
return championInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const championId = getRouterParam(event, "id")
|
||||||
|
const client = await connectToDatabase();
|
||||||
|
const latestPatch = await fetchLatestPatch(client);
|
||||||
|
const data = await championInfos(client, latestPatch, championId);
|
||||||
|
await client.close()
|
||||||
|
return data
|
||||||
|
})
|
||||||
3
frontend/server/tsconfig.json
Normal file
3
frontend/server/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../.nuxt/tsconfig.server.json"
|
||||||
|
}
|
||||||
4
frontend/tsconfig.json
Normal file
4
frontend/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
||||||
8
frontend/utils/cdragon.js
Normal file
8
frontend/utils/cdragon.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const CDRAGON_BASE = "https://raw.communitydragon.org/latest/"
|
||||||
|
|
||||||
|
function mapPath(assetPath) {
|
||||||
|
if(assetPath === undefined || assetPath === null) return ""
|
||||||
|
return assetPath.toLowerCase().replace("/lol-game-data/assets/", "plugins/rcp-be-lol-game-data/global/default/")
|
||||||
|
}
|
||||||
|
|
||||||
|
export { mapPath, CDRAGON_BASE}
|
||||||
1
match_collector/.gitignore
vendored
Normal file
1
match_collector/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
8
match_collector/Dockerfile
Normal file
8
match_collector/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
FROM node:lts-alpine
|
||||||
|
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
USER node
|
||||||
|
COPY --chown=node:node package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY --chown=node:node . .
|
||||||
|
CMD [ "node", "index.js" ]
|
||||||
105
match_collector/champion_stat.js
Normal file
105
match_collector/champion_stat.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
function sameArrays(array1, array2) {
|
||||||
|
if(array1.length != array2.length) return false;
|
||||||
|
for(let e of array1) {
|
||||||
|
if(!array2.includes(e)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function championInfos(client, patch, championId) {
|
||||||
|
const database = client.db("matches");
|
||||||
|
const matches = database.collection(patch)
|
||||||
|
const allMatches = matches.find()
|
||||||
|
|
||||||
|
let winningMatches = 0;
|
||||||
|
let losingMatches = 0;
|
||||||
|
let totalMatches = 0;
|
||||||
|
const runes = [];
|
||||||
|
for await (let match of allMatches) {
|
||||||
|
totalMatches += 1;
|
||||||
|
for(let participant of match.info.participants) {
|
||||||
|
if(participant.championId != championId) continue;
|
||||||
|
|
||||||
|
if(participant.win)
|
||||||
|
winningMatches += 1;
|
||||||
|
else
|
||||||
|
losingMatches += 1;
|
||||||
|
|
||||||
|
const primaryStyle = participant.perks.styles[0].style
|
||||||
|
const secondaryStyle = participant.perks.styles[1].style
|
||||||
|
const selections = []
|
||||||
|
for(let style of participant.perks.styles) {
|
||||||
|
for(let perk of style.selections) {
|
||||||
|
selections.push(perk.perk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const gameRunes = {count:1, primaryStyle: primaryStyle, secondaryStyle: secondaryStyle, selections: selections};
|
||||||
|
let addRunes = true;
|
||||||
|
for(let rune of runes) {
|
||||||
|
if(rune.primaryStyle == gameRunes.primaryStyle
|
||||||
|
&& rune.secondaryStyle == gameRunes.secondaryStyle
|
||||||
|
&& sameArrays(rune.selections, gameRunes.selections)) {
|
||||||
|
rune.count++; addRunes = false; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(addRunes) runes.push(gameRunes)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter runes to keep 3 most played
|
||||||
|
const maxes = [0, 0, 0] // 24, 2, 1 -> 18 > 1 -> 24, 2, 2 -> 18 > 2 -> 24, 24, 2
|
||||||
|
const maxRunes = [null, null, null]
|
||||||
|
for(let rune of runes) {
|
||||||
|
let maxcount = 2;
|
||||||
|
if(rune.count <= maxes[maxcount]) continue;
|
||||||
|
|
||||||
|
while(maxcount > 0 && rune.count > maxes[maxcount]) {
|
||||||
|
maxes[maxcount] = maxes[maxcount - 1];
|
||||||
|
maxRunes[maxcount] = maxRunes[maxcount - 1];
|
||||||
|
maxcount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
rune.pickrate = rune.count / (winningMatches + losingMatches)
|
||||||
|
if(rune.count <= maxes[maxcount]) maxcount++;
|
||||||
|
maxes[maxcount] = rune.count
|
||||||
|
maxRunes[maxcount] = rune
|
||||||
|
}
|
||||||
|
|
||||||
|
return {id: championId,
|
||||||
|
winrate:winningMatches / (winningMatches + losingMatches),
|
||||||
|
gameCount:(winningMatches + losingMatches),
|
||||||
|
pickrate:(winningMatches + losingMatches)/totalMatches,
|
||||||
|
runes: maxRunes.filter((x) => x != null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeChampionStat(client, patch, championId) {
|
||||||
|
const championInfo = await championInfos(client, patch, championId)
|
||||||
|
const database = client.db("champions")
|
||||||
|
const collection = database.collection(patch)
|
||||||
|
await collection.updateOne({id: championInfo.id}, {$set: championInfo}, { upsert: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function championList() {
|
||||||
|
const response = await fetch("https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json");
|
||||||
|
const list = await response.json()
|
||||||
|
return list.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeChampionsStats(client, patch) {
|
||||||
|
const list = await championList()
|
||||||
|
console.log("Generating stats for " + list.length + " champions")
|
||||||
|
let i = 0;
|
||||||
|
for(let champion of list) {
|
||||||
|
console.log("Entry " + i + "/" + list.length + " ...")
|
||||||
|
await makeChampionStat(client, patch, champion.id)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const database = client.db("champions")
|
||||||
|
const collection = database.collection(patch)
|
||||||
|
await collection.createIndex({id:1})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {makeChampionsStats}
|
||||||
156
match_collector/index.js
Normal file
156
match_collector/index.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
const base = "https://euw1.api.riotgames.com"
|
||||||
|
const api_key = process.env.RIOT_API_KEY
|
||||||
|
const sleep_minutes = 3
|
||||||
|
|
||||||
|
var champion_stat = require("./champion_stat")
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log("MatchCollector - Hello !");
|
||||||
|
const client = await connectToDatabase();
|
||||||
|
const [latestPatch, latestPatchTime] = await fetchLatestPatchDate(client);
|
||||||
|
console.log("Connected to database, latest patch " + latestPatch + " was epoch: " + latestPatchTime)
|
||||||
|
|
||||||
|
const alreadySeenGameList = await alreadySeenGames(client, latestPatch);
|
||||||
|
console.log("We already have " + alreadySeenGameList.length + " matches for this patch !")
|
||||||
|
|
||||||
|
const challengerLeague = await fetchChallengerLeague();
|
||||||
|
console.log("ChallengerLeague: got " + challengerLeague.entries.length + " entries");
|
||||||
|
|
||||||
|
const gameList = [];
|
||||||
|
let i = 0;
|
||||||
|
for(let challenger of challengerLeague.entries) {
|
||||||
|
console.log("Entry " + i + "/" + challengerLeague.entries.length + " ...")
|
||||||
|
const puuid = await summonerPuuid(challenger.summonerId);
|
||||||
|
const challengerGameList = await summonerGameList(puuid, latestPatchTime);
|
||||||
|
for(let game of challengerGameList) {
|
||||||
|
if(!gameList.includes(game) && !alreadySeenGameList.includes(game)) {
|
||||||
|
gameList.push(game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Games: got " + gameList.length + " entries");
|
||||||
|
i = 0;
|
||||||
|
for(let game of gameList) {
|
||||||
|
console.log("Entry " + i + "/" + gameList.length + " ...")
|
||||||
|
const gameMatch = await match(game)
|
||||||
|
const gameTimeline = await matchTimeline(game)
|
||||||
|
gameMatch.timeline = gameTimeline
|
||||||
|
await saveMatch(client, gameMatch, latestPatch)
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Generating stats...");
|
||||||
|
await champion_stat.makeChampionsStats(client, latestPatch)
|
||||||
|
|
||||||
|
console.log("All done. Closing client.");
|
||||||
|
await client.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRateLimit(url) {
|
||||||
|
let response = await fetch(url)
|
||||||
|
if(response.status == 429) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, sleep_minutes * 60 * 1000))
|
||||||
|
response = handleRateLimit(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(response) {
|
||||||
|
if(!response.ok) {
|
||||||
|
console.log("Error during fetch(" + response.url + "): STATUS " + response.status + " (" + response.statusText + ")");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connectToDatabase() {
|
||||||
|
const { MongoClient, ServerApiVersion } = require("mongodb");
|
||||||
|
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
||||||
|
const client = new MongoClient(`mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@mongo:27017`)
|
||||||
|
await client.connect()
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatestPatchDate(client) {
|
||||||
|
const database = client.db("patches");
|
||||||
|
const patches = database.collection("patches");
|
||||||
|
const latestPatch = await patches.find().limit(1).sort({date:-1}).next()
|
||||||
|
return [latestPatch.patch, Math.floor(latestPatch.date.valueOf() / 1000)]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchChallengerLeague() {
|
||||||
|
const queue = "RANKED_SOLO_5x5"
|
||||||
|
const endpoint = `/lol/league/v4/challengerleagues/by-queue/${queue}`
|
||||||
|
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||||
|
|
||||||
|
const challengerLeagueResponse = await handleRateLimit(url);
|
||||||
|
|
||||||
|
handleError(challengerLeagueResponse)
|
||||||
|
|
||||||
|
const challengerLeague = await challengerLeagueResponse.json();
|
||||||
|
return challengerLeague;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function summonerPuuid(summonerId) {
|
||||||
|
const endpoint = `/lol/summoner/v4/summoners/${summonerId}`
|
||||||
|
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||||
|
|
||||||
|
const puuidResponse = await handleRateLimit(url);
|
||||||
|
handleError(puuidResponse)
|
||||||
|
const puuidJson = await puuidResponse.json();
|
||||||
|
|
||||||
|
return puuidJson.puuid;
|
||||||
|
}
|
||||||
|
async function summonerGameList(puuid, startTime) {
|
||||||
|
const base = "https://europe.api.riotgames.com"
|
||||||
|
const endpoint = `/lol/match/v5/matches/by-puuid/${puuid}/ids`;
|
||||||
|
const url = `${base}${endpoint}?queue=420&type=ranked&startTime=${startTime}&api_key=${api_key}`
|
||||||
|
|
||||||
|
const gameListResponse = await handleRateLimit(url);
|
||||||
|
handleError(gameListResponse)
|
||||||
|
const gameList = await gameListResponse.json();
|
||||||
|
|
||||||
|
return gameList;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function match(matchId) {
|
||||||
|
const base = "https://europe.api.riotgames.com"
|
||||||
|
const endpoint = `/lol/match/v5/matches/${matchId}`
|
||||||
|
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||||
|
|
||||||
|
const matchResponse = await handleRateLimit(url)
|
||||||
|
handleError(matchResponse)
|
||||||
|
const match = await matchResponse.json();
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function matchTimeline(matchId) {
|
||||||
|
const base = "https://europe.api.riotgames.com"
|
||||||
|
const endpoint = `/lol/match/v5/matches/${matchId}/timeline`
|
||||||
|
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||||
|
|
||||||
|
const timelineResponse = await handleRateLimit(url)
|
||||||
|
handleError(timelineResponse)
|
||||||
|
const timeline = await timelineResponse.json();
|
||||||
|
|
||||||
|
return timeline
|
||||||
|
}
|
||||||
|
|
||||||
|
async function alreadySeenGames(client, latestPatch) {
|
||||||
|
const database = client.db("matches")
|
||||||
|
const matches = database.collection(latestPatch)
|
||||||
|
|
||||||
|
const alreadySeen = await matches.distinct("metadata.matchId")
|
||||||
|
return alreadySeen
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveMatch(client, match, latestPatch) {
|
||||||
|
const database = client.db("matches")
|
||||||
|
const matches = database.collection(latestPatch)
|
||||||
|
await matches.insertOne(match)
|
||||||
|
}
|
||||||
163
match_collector/package-lock.json
generated
Normal file
163
match_collector/package-lock.json
generated
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
{
|
||||||
|
"name": "stat_collector",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "stat_collector",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"mongodb": "^6.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
|
"version": "1.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
|
||||||
|
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sparse-bitfield": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/webidl-conversions": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/whatwg-url": {
|
||||||
|
"version": "11.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||||
|
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/webidl-conversions": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bson": {
|
||||||
|
"version": "6.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz",
|
||||||
|
"integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/memory-pager": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/mongodb": {
|
||||||
|
"version": "6.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz",
|
||||||
|
"integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@mongodb-js/saslprep": "^1.1.5",
|
||||||
|
"bson": "^6.7.0",
|
||||||
|
"mongodb-connection-string-url": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@aws-sdk/credential-providers": "^3.188.0",
|
||||||
|
"@mongodb-js/zstd": "^1.1.0",
|
||||||
|
"gcp-metadata": "^5.2.0",
|
||||||
|
"kerberos": "^2.0.1",
|
||||||
|
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||||
|
"snappy": "^7.2.2",
|
||||||
|
"socks": "^2.7.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@aws-sdk/credential-providers": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@mongodb-js/zstd": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"gcp-metadata": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"kerberos": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"mongodb-client-encryption": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"snappy": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"socks": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/whatwg-url": "^11.0.2",
|
||||||
|
"whatwg-url": "^13.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/punycode": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sparse-bitfield": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"memory-pager": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "13.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
|
||||||
|
"integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "^4.1.1",
|
||||||
|
"webidl-conversions": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
match_collector/package.json
Normal file
15
match_collector/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "match_collector",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "commonjs",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"mongodb": "^6.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
patch_detector/.gitignore
vendored
Normal file
1
patch_detector/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
8
patch_detector/Dockerfile
Normal file
8
patch_detector/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
FROM node:lts-alpine
|
||||||
|
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
USER node
|
||||||
|
COPY --chown=node:node package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY --chown=node:node . .
|
||||||
|
CMD [ "node", "index.js" ]
|
||||||
53
patch_detector/index.js
Normal file
53
patch_detector/index.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const { MongoClient, ServerApiVersion } = require("mongodb");
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const client = await connectToDatabase();
|
||||||
|
const newPatch = await fetchLatestPatch();
|
||||||
|
const newDate = new Date()
|
||||||
|
if(!(await compareLatestSavedPatch(client, newPatch, newDate))) {
|
||||||
|
downloadAssets()
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchLatestPatch() {
|
||||||
|
const url = "https://ddragon.leagueoflegends.com/api/versions.json"
|
||||||
|
const patchDataResponse = await fetch(url);
|
||||||
|
const patchData = await patchDataResponse.json();
|
||||||
|
const patch = patchData[0];
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connectToDatabase() {
|
||||||
|
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
||||||
|
const client = new MongoClient(`mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@mongo:27017`, {
|
||||||
|
serverApi: {
|
||||||
|
version: ServerApiVersion.v1,
|
||||||
|
strict: true,
|
||||||
|
deprecationErrors: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await client.connect()
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
async function compareLatestSavedPatch(client, newPatch, newDate) {
|
||||||
|
const database = client.db("patches")
|
||||||
|
const patches = database.collection("patches")
|
||||||
|
const latestPatch = await patches.find().limit(1).sort({date:-1}).next()
|
||||||
|
|
||||||
|
if(latestPatch.patch != newPatch) {
|
||||||
|
await patches.insertOne({patch:newPatch, date:newDate})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAssets() {
|
||||||
|
|
||||||
|
}
|
||||||
163
patch_detector/package-lock.json
generated
Normal file
163
patch_detector/package-lock.json
generated
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
{
|
||||||
|
"name": "patch_detector",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "patch_detector",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"mongodb": "^6.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
|
"version": "1.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
|
||||||
|
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sparse-bitfield": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/webidl-conversions": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/whatwg-url": {
|
||||||
|
"version": "11.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||||
|
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/webidl-conversions": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bson": {
|
||||||
|
"version": "6.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz",
|
||||||
|
"integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/memory-pager": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/mongodb": {
|
||||||
|
"version": "6.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz",
|
||||||
|
"integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@mongodb-js/saslprep": "^1.1.5",
|
||||||
|
"bson": "^6.7.0",
|
||||||
|
"mongodb-connection-string-url": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@aws-sdk/credential-providers": "^3.188.0",
|
||||||
|
"@mongodb-js/zstd": "^1.1.0",
|
||||||
|
"gcp-metadata": "^5.2.0",
|
||||||
|
"kerberos": "^2.0.1",
|
||||||
|
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||||
|
"snappy": "^7.2.2",
|
||||||
|
"socks": "^2.7.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@aws-sdk/credential-providers": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@mongodb-js/zstd": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"gcp-metadata": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"kerberos": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"mongodb-client-encryption": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"snappy": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"socks": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/whatwg-url": "^11.0.2",
|
||||||
|
"whatwg-url": "^13.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/punycode": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sparse-bitfield": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"memory-pager": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "13.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
|
||||||
|
"integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "^4.1.1",
|
||||||
|
"webidl-conversions": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
patch_detector/package.json
Normal file
15
patch_detector/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "patch_detector",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "commonjs",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"mongodb": "^6.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user