Compare commits

..

10 Commits

11 changed files with 167 additions and 97 deletions

4
app/package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "app", "name": "app",
"version": "1.1.0", "version": "1.1.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "app", "name": "app",
"version": "1.1.0", "version": "1.1.1",
"dependencies": { "dependencies": {
"@capacitor/android": "6.1.2", "@capacitor/android": "6.1.2",
"@capacitor/app": "6.0.1", "@capacitor/app": "6.0.1",

View File

@ -1,7 +1,7 @@
{ {
"name": "app", "name": "app",
"private": true, "private": true,
"version": "1.1.0", "version": "1.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -95,7 +95,7 @@ const logoutUser = () => {
<ion-icon slot="icon-only" :icon="chevronBack"></ion-icon> <ion-icon slot="icon-only" :icon="chevronBack"></ion-icon>
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>
<ion-title>Menu Content</ion-title> <ion-title>User Menu</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>

View File

@ -30,7 +30,7 @@ export class BullpenSessionService extends ApiService<Bullpen> {
} }
} }
public findByPitcherId(id: number, page: number, size: number): Promise<Pageable<Bullpen>> { public fetchAllByUser(id: number, page: number, size: number): Promise<Pageable<Bullpen>> {
return api return api
.get('/bullpen_session', { params: { .get('/bullpen_session', { params: {
user: id, user: id,

View File

@ -15,20 +15,20 @@ class PlayerService extends ApiService<Player> {
gender: 'male', gender: 'male',
bats: 'right', bats: 'right',
throws: 'right', throws: 'right',
height: 187, height: 0,
weight: 84, weight: 0,
state: 'active', state: 'active',
jerseyNumber: 23, jerseyNumber: 0,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
user: { user: {
id: 0, id: 0,
auth: { auth: {
email: 'test@de.de', email: '',
password: 'test$123' password: ''
}, },
firstName: 'Demo', firstName: '',
lastName: 'Player', lastName: '',
dateOfBirth: new Date(), dateOfBirth: new Date(),
roles: ['player'], roles: ['player'],
createdAt: new Date(), createdAt: new Date(),

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, ref} from 'vue'; import {computed, ref, reactive} from 'vue';
import { import {
IonPage, IonPage,
IonHeader, IonHeader,
@ -7,24 +7,33 @@ import {
IonTitle, IonTitle,
IonContent, IonContent,
IonAvatar, IonAvatar,
IonButton,
IonButtons, IonButtons,
IonIcon, IonIcon,
IonMenuButton, IonMenuButton,
IonFab, IonFab,
IonFabButton IonFabButton,
IonInfiniteScroll,
IonInfiniteScrollContent,
InfiniteScrollCustomEvent,
IonList,
IonItem,
IonLabel, IonBadge,
} from '@ionic/vue'; } from '@ionic/vue';
import { import {
playOutline, playOutline,
statsChartOutline, statsChartOutline,
personOutline, personOutline,
baseball,
add, add,
personAddOutline personAddOutline, baseballOutline
} from 'ionicons/icons'; } from 'ionicons/icons';
import {useStore} from "vuex"; import {useStore} from "vuex";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import BullpenSessionService from "@/services/BullpenSessionService"; import BullpenSessionService from "@/services/BullpenSessionService";
import TokenService from "@/services/TokenService"; import TokenService from "@/services/TokenService";
import dayjs from "dayjs";
import Bullpen from "@/types/Bullpen";
import PitchType from "@/types/PitchType";
const router = useRouter(); const router = useRouter();
const store = useStore(); const store = useStore();
@ -60,6 +69,39 @@ const addPlayer = () => {
router.push({ path: '/player/create' }); router.push({ path: '/player/create' });
} }
const items = reactive<Bullpen[]>([]);
const page = ref(0);
const generateItems = () => {
BullpenSessionService
.fetchAllByUser(user.value.id, page.value, 10)
.then((bullpens) => {
++page.value;
bullpens.data.forEach((bullpen: Bullpen) => {
items.push(bullpen);
})
}, error => {
console.log(error);
});
};
const formatDate = (date: Date) => {
return dayjs(date).format('YYYY.MM.DD HH:mm');
}
// const determinePitchTypeName = (id: number): string => {
// const pitchType = store.state.pitchTypes.find((pitchType: PitchType) => pitchType.id === id);
//
// return pitchType?.name ?? 'Unknown';
// }
const ionInfinite = (event: InfiniteScrollCustomEvent) => {
generateItems();
setTimeout(() => event.target.complete(), 500);
};
generateItems();
</script> </script>
<template> <template>
@ -75,7 +117,24 @@ const addPlayer = () => {
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content class="ion-padding"> <ion-content>
<ion-list>
<ion-item v-for="(bullpen, index) in items" :key="index">
<ion-icon aria-hidden="true" :icon="baseball" slot="start"></ion-icon>
<ion-label>Bullpen from ({{formatDate(bullpen.startedAt)}})</ion-label>
<!-- <ion-list v-for="(pitch, index) in bullpen.pitches" :key="index">-->
<!-- <ion-item>-->
<!-- <ion-icon slot="start" :icon="baseballOutline" class="icon-login"></ion-icon>-->
<!-- <ion-label>{{determinePitchTypeName(pitch.pitchTypeId)}}</ion-label>-->
<!-- <ion-badge>{{pitch.aimedArea}}</ion-badge>-->
<!-- <ion-badge>{{pitch.hitArea}}</ion-badge>-->
<!-- </ion-item>-->
<!-- </ion-list>-->
</ion-item>
</ion-list>
<ion-infinite-scroll @ionInfinite="ionInfinite">
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
<!-- <div class="user-container">--> <!-- <div class="user-container">-->
<!-- <ion-avatar class="user-avatar">--> <!-- <ion-avatar class="user-avatar">-->
<!-- <template v-if="userImage">--> <!-- <template v-if="userImage">-->

View File

@ -1,37 +1,12 @@
services: services:
db: postgres:
image: postgres:15 image: postgres:14-alpine
restart: unless-stopped container_name: postgres14
env_file: ./.env.development
environment:
POSTGRES_DB: $DB_NAME
POSTGRES_USER: $DB_USER
POSTGRES_PASSWORD: $DB_PASSWORD
ports: ports:
- $DB_PORT:$DB_PORT - "5432:5432"
volumes: volumes:
- db_data:/var/lib/postgresql/data - ~/apps/postgres:/var/lib/postgresql/data
app-dev:
image: node:20-alpine
restart: unless-stopped
env_file: ./.env.development
working_dir: /app
volumes:
- .:/app
- /app/node_modules
ports:
- '$NODE_LOCAL_PORT:$NODE_DOCKER_PORT'
environment: environment:
NODE_ENV: development POSTGRES_USER: postgres
DB_HOST: db POSTGRES_PASSWORD: postgres
DB_PORT: $DB_PORT POSTGRES_DB: bullpen-dev
DB_NAME: $DB_NAME
DB_USER: $DB_USER
DB_PASS: $DB_PASSWORD
command: sh -c "npm install && npm run dev"
depends_on:
- db
volumes:
db_data:

View File

@ -1,12 +1,12 @@
{ {
"name": "bullpen-backend", "name": "bullpen-backend",
"version": "1.1.0", "version": "1.1.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bullpen-backend", "name": "bullpen-backend",
"version": "1.1.0", "version": "1.1.1",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",

View File

@ -1,6 +1,6 @@
{ {
"name": "bullpen-backend", "name": "bullpen-backend",
"version": "1.1.0", "version": "1.1.1",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"prebuild": "node -p \"'const BULLPEN_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\"\n\nmodule.exports = BULLPEN_VERSION; > config/version.js", "prebuild": "node -p \"'const BULLPEN_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\"\n\nmodule.exports = BULLPEN_VERSION; > config/version.js",
@ -15,6 +15,9 @@
"start:prod": "npm run cross-env NODE_ENV=production node server.js", "start:prod": "npm run cross-env NODE_ENV=production node server.js",
"start:dev": "cross-env NODE_ENV=development nodemon server.js", "start:dev": "cross-env NODE_ENV=development nodemon server.js",
"migrate:dev": "cross-env NODE_ENV=development npx sequelize-cli db:migrate", "migrate:dev": "cross-env NODE_ENV=development npx sequelize-cli db:migrate",
"seed:bullpen": "cross-env NODE_ENV=development npx sequelize-cli db:seed --seed 05-bullpens.js",
"dev:db:start": "docker-compose -f docker-compose.dev.yml up -d",
"dev:db:stop": "docker-compose -f docker-compose.dev.yml down -v",
"test:db:start": "docker-compose -f docker-compose.test.yml up -d", "test:db:start": "docker-compose -f docker-compose.test.yml up -d",
"test:db:stop": "docker-compose -f docker-compose.test.yml down -v", "test:db:stop": "docker-compose -f docker-compose.test.yml down -v",
"test:run": "dotenv -e .env.test -- jest --runInBand --detectOpenHandles", "test:run": "dotenv -e .env.test -- jest --runInBand --detectOpenHandles",

View File

@ -16,5 +16,8 @@ docker-compose up --build -d
docker-compose exec app npx sequelize-cli db:migrate docker-compose exec app npx sequelize-cli db:migrate
docker-compose exec app npx sequelize-cli db:seed:all docker-compose exec app npx sequelize-cli db:seed:all
// seed one specific file
docker-compose exec app npx sequelize db:seed --seed my_seeder_file.js
docker-compose exec app sh docker-compose exec app sh
``` ```

View File

@ -1,48 +1,69 @@
const process = require('process'); const process = require('process');
const {createPlayer, createUsers} = require("../helper/seeder.helper"); const {createPlayer, createUsers} = require("../helper/seeder.helper");
const createBullpen = async (queryInterface, demoPlayer) => {
const pickRandomValueFromArray = (values) => {
return values[Math.floor(Math.random() * values.length)];
}
const shouldMatch = (percentage) => {
return Math.random() * 100 < percentage;
}
const createBullpens = async (queryInterface, demoPlayer) => {
const startDate = new Date(); const startDate = new Date();
const endDate = new Date(new Date().setTime(startDate.getTime() + 10 * 60000)); // const endDate = new Date(new Date().setTime(startDate.getTime() + (10 * 60000)));
// create 20 bullpens for demo player
const bullpens = [...Array(20).keys()].map(key => { const bullpens = [...Array(20).keys()].map(key => {
const days = 5 + key;
const startedAt = new Date(new Date().setDate(startDate.getDate() - days));
return { return {
pitcherId: demoPlayer.user.id, pitcherId: demoPlayer.userId,
startedAt: new Date(new Date().setDate(startDate.getDate()-5)), startedAt: startedAt,
finishedAt: new Date(new Date().setDate(startDate.getDate()-5)), finishedAt: new Date(new Date().setTime(startedAt.getTime() + (10 * 60000))),
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date() updatedAt: new Date()
} }
}) })
await queryInterface.bulkInsert('BullpenSessions', bullpens); await queryInterface.bulkInsert('BullpenSessions', bullpens);
const pitchTypeIds = await queryInterface const pitchTypeIds = (await queryInterface
.select(null, 'PitchTypes') .select(null, 'PitchTypes'))
.map(pitchType => pitchType.id); .map(pitchType => pitchType.id);
// red: 21-28 // red: 21-28
// orange: 11-18: // orange: 11-18:
// green: 1-9 // green: 1-9
const bullpenChart = [ const bullpenChart = [
[21,22,23,24,25,26,27,28], 1,2,3,4,5,6,7,8,9,
[11,12,13,14,15,16,17,18], 11,12,13,14,15,16,17,18,
[1,2,3,4,5,6,7,8,9] 21,22,23,24,25,26,27,28
] ];
const dbBullpens = await queryInterface.select(null, 'BullpenSessions', { const dbBullpens = await queryInterface.select(null, 'BullpenSessions', {
where: { where: {
pitcherId: demoPlayer.user.id pitcherId: demoPlayer.userId
} }
}); });
[...Array(20).keys()].map(key => { // fill pitches for each bullpen
return { const pitches = dbBullpens.map(bullpen => {
pitchTypeId: pitchTypeIds[Math.floor(Math.random() * pitchTypeIds.length)], return [...Array(20).keys()].map(key => {
pitchTime: new Date(2025, 3, 22, 16, 7, 21), const aimedArea = pickRandomValueFromArray(bullpenChart);
aimedArea: 11, const hitArea = shouldMatch(30) ? aimedArea : pickRandomValueFromArray(bullpenChart);
hitArea: 12 return {
} pitchTypeId: pickRandomValueFromArray(pitchTypeIds),
bullpenSessionId: bullpen.id,
pitchTime: new Date(new Date().setTime(bullpen.startedAt.getTime() + key * 60000)),
aimedArea: aimedArea,
hitArea: hitArea,
createdAt: new Date(),
updatedAt: new Date()
};
});
}); });
await queryInterface.bulkInsert('Pitches', pitches.flat());
} }
/** @type {import('sequelize-cli').Migration} */ /** @type {import('sequelize-cli').Migration} */
@ -50,34 +71,43 @@ module.exports = {
async up (queryInterface, /*Sequelize*/) { async up (queryInterface, /*Sequelize*/) {
if (process.env.NODE_ENV !== 'development') return; if (process.env.NODE_ENV !== 'development') return;
const roles = await queryInterface.select(null, 'Roles'); let demoPlayer = (await queryInterface.select(null, 'Players', {
const players = [{ where: {
email: 'demo@bullpen.com', height: 187,
password: 'demo$123', weight: 85,
firstName: 'Demo', }
lastName: 'Player', })).shift();
dateOfBirth: new Date(1975, 6, 19),
jerseyNumber: 33,
height: 187,
weight: 85,
gender: 'male',
bats: 'right',
throws: 'right',
state: 'active'
}];
const dbPlayers = await createPlayer(queryInterface, roles, players); if (demoPlayer === undefined) {
const demoPlayer = dbPlayers const roles = await queryInterface.select(null, 'Roles');
.filter(player => player.user.auth.email === 'demo@bullpen.com').shift(); const players = [{
email: 'demo@bullpen.com',
password: 'demo$123',
firstName: 'Demo',
lastName: 'Player',
dateOfBirth: new Date(1975, 6, 19),
jerseyNumber: 33,
height: 187,
weight: 85,
gender: 'male',
bats: 'right',
throws: 'right',
state: 'active'
}];
for (let i = 0; i < 10; ++i) { await createPlayer(queryInterface, roles, players);
createBullpen(queryInterface, demoPlayer); demoPlayer = (await queryInterface.select(null, 'Players', {
where: {
height: 187,
weight: 85,
}
})).shift();
} }
await createBullpens(queryInterface, demoPlayer);
}, },
async down (queryInterface, /*Sequelize*/) { async down (/*queryInterface,*/ /*Sequelize*/) {
if (process.env.NODE_ENV !== 'development') return; // if (process.env.NODE_ENV !== 'development') return;
return;
} }
}; };