Compare commits
3 Commits
9071886991
...
c40c931209
| Author | SHA1 | Date |
|---|---|---|
|
|
c40c931209 | |
|
|
1fa86d69ae | |
|
|
c4725a5d73 |
|
|
@ -1,3 +1,5 @@
|
||||||
.idea/
|
.idea/
|
||||||
node_modules/
|
node_modules/
|
||||||
backend/.env
|
backend/.env.test
|
||||||
|
backend/.env.development
|
||||||
|
backend/.env.production
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
/>
|
/>
|
||||||
<meta name="format-detection" content="telephone=no" />
|
<meta name="format-detection" content="telephone=no" />
|
||||||
<meta name="msapplication-tap-highlight" content="no" />
|
<meta name="msapplication-tap-highlight" content="no" />
|
||||||
|
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">-->
|
||||||
|
|
||||||
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
|
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import { RouteRecordRaw } from 'vue-router';
|
||||||
import PitcherList from '../views/PitcherList.vue'
|
import PitcherList from '../views/PitcherList.vue'
|
||||||
import LoginView from '../views/LoginView.vue'
|
import LoginView from '../views/LoginView.vue'
|
||||||
import HomeView from '../views/HomeView.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
import PreparePitch from "@/views/PreparePitch.vue";
|
import BullpenView from "@/views/BullpenView.vue";
|
||||||
import FinalizePitch from "@/views/FinalizePitch.vue";
|
import BullpenSummaryView from "@/views/BullpenSummaryView.vue";
|
||||||
import BullpenStats from "@/views/BullpenStats.vue";
|
|
||||||
import SetupView from '@/views/SetupView.vue';
|
import SetupView from '@/views/SetupView.vue';
|
||||||
|
import backendService from '@/services/BackendService'
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{ path: '/', redirect: '/login' },
|
{ path: '/', redirect: '/login' },
|
||||||
|
|
@ -15,10 +15,8 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
{ path: '/setup', component: SetupView },
|
{ path: '/setup', component: SetupView },
|
||||||
{ path: '/home', component: HomeView },
|
{ path: '/home', component: HomeView },
|
||||||
{ path: '/pitchers', component: PitcherList },
|
{ path: '/pitchers', component: PitcherList },
|
||||||
{ path: '/bullpen', component: PreparePitch },
|
{ path: '/bullpen', component: BullpenView },
|
||||||
{ path: '/prepare', component: PreparePitch },
|
{ path: '/summary', component: BullpenSummaryView }
|
||||||
{ path: '/finalize', component: FinalizePitch },
|
|
||||||
{ path: '/stats', component: BullpenStats }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|
@ -28,20 +26,19 @@ const router = createRouter({
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const serverAddress = localStorage.getItem('serverAddress');
|
|
||||||
const isAuthenticated = store.state.auth.isAuthenticated;
|
const isAuthenticated = store.state.auth.isAuthenticated;
|
||||||
|
|
||||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||||
return next('/login');
|
return next('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverAddress && to.path !== '/setup') {
|
if (!backendService.hasServer() && to.path !== '/setup') {
|
||||||
return next('/setup');
|
return next('/setup');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverAddress && to.path === '/setup') {
|
// if (serverAddress && to.path === '/setup') {
|
||||||
return next('/login');
|
// return next('/login');
|
||||||
}
|
// }
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
const server = JSON.parse(localStorage.getItem("server") || '""');
|
||||||
|
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL: "http://localhost:8080/api",
|
baseURL: `${server.protocol}://${server.host}:${server.port}/api`,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Server from "@/types/Server";
|
||||||
|
|
||||||
|
class BackendService {
|
||||||
|
hasServer(): boolean {
|
||||||
|
return localStorage.getItem("server") !== null;
|
||||||
|
}
|
||||||
|
getServer(): Server {
|
||||||
|
return JSON.parse(localStorage.getItem("server") || '{"protocol":"https","host":"localhost","port":8080}');
|
||||||
|
}
|
||||||
|
updateServer(server: Server): void {
|
||||||
|
localStorage.setItem("server", JSON.stringify(server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new BackendService();
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import Pitch from "@/types/Pitch";
|
import Pitch from "@/types/Pitch";
|
||||||
import User from "@/types/User";
|
import User from "@/types/User";
|
||||||
import Bullpen from '@/types/Bullpen';
|
import Bullpen from '@/types/Bullpen';
|
||||||
import PitchTypeService from '@/services/PitchTypeService';
|
|
||||||
import api from '@/services/Api';
|
import api from '@/services/Api';
|
||||||
|
|
||||||
export class BullpenSessionService {
|
export class BullpenSessionService {
|
||||||
|
|
@ -18,10 +17,9 @@ export class BullpenSessionService {
|
||||||
public save(bullpen: Bullpen): Promise<Bullpen> {
|
public save(bullpen: Bullpen): Promise<Bullpen> {
|
||||||
console.log(JSON.stringify(bullpen, null, 2));
|
console.log(JSON.stringify(bullpen, null, 2));
|
||||||
return api
|
return api
|
||||||
.post('/bullpen_session', {
|
.post('/bullpen_session', bullpen)
|
||||||
bullpen
|
|
||||||
})
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
console.log(JSON.stringify(response.data, null, 2));
|
||||||
return response.data;
|
return response.data;
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
console.log(JSON.stringify(error, null, 2));
|
console.log(JSON.stringify(error, null, 2));
|
||||||
|
|
@ -30,9 +28,9 @@ export class BullpenSessionService {
|
||||||
|
|
||||||
public createPitch(): Pitch {
|
public createPitch(): Pitch {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
id: undefined,
|
||||||
pitchTime: new Date(),
|
pitchTime: new Date(),
|
||||||
pitchTypeId: PitchTypeService.getLocalPitchTypes()[0].id,
|
pitchTypeId: 0,
|
||||||
aimedArea: 0,
|
aimedArea: 0,
|
||||||
hitArea: 0
|
hitArea: 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
export default interface Pitch {
|
export default interface Pitch {
|
||||||
id: number,
|
id: number | undefined,
|
||||||
pitchTime: Date,
|
pitchTime: Date,
|
||||||
pitchTypeId: number,
|
pitchTypeId: number,
|
||||||
aimedArea: number,
|
aimedArea: number,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default interface Server {
|
||||||
|
protocol: string,
|
||||||
|
host: string,
|
||||||
|
port: number
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
IonTitle,
|
IonTitle,
|
||||||
IonToolbar
|
IonToolbar
|
||||||
} from '@ionic/vue';
|
} from '@ionic/vue';
|
||||||
|
import PitchType from "@/types/PitchType";
|
||||||
import {useRouter} from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
import {computed} from 'vue';
|
import {computed} from 'vue';
|
||||||
import {useStore} from 'vuex';
|
import {useStore} from 'vuex';
|
||||||
|
|
@ -23,23 +24,36 @@ import {useStore} from 'vuex';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
|
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
|
||||||
const pitcher = computed(() => store.state.auth.user);
|
const pitcher = computed(() => store.state.auth.user);
|
||||||
const bullpen = computed(() => store.state.bullpen.bullpen);
|
const pitchTypes = computed(() => store.state.pitchTypes.pitchTypes);
|
||||||
|
const bullpen = computed(() => store.state.bullpen);
|
||||||
|
|
||||||
|
console.log(JSON.stringify(bullpen.value.bullpen, null, 2));
|
||||||
|
|
||||||
|
const determinePitchTypeName = (id: number): string => {
|
||||||
|
const pitchType = pitchTypes.value.find((pitchType: PitchType) => pitchType.id === id);
|
||||||
|
|
||||||
|
return pitchType?.name ?? 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
const gotoHome = () => {
|
const gotoHome = () => {
|
||||||
router.push('/home');
|
store.dispatch("bullpen/finish", bullpen.value.bullpen);
|
||||||
};
|
router.push({path: '/home'});
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-header :translucent="true">
|
<ion-header :translucent="true">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Bullpen Stats for {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
|
<ion-title v-if="isAuthenticated">Bullpen Summary {{ pitcher.firstName }} {{ pitcher.lastName }}</ion-title>
|
||||||
|
<ion-title v-else>Bullpen Summary</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-card v-for="(pitch, index) in bullpen.pitches" :key="index">
|
<ion-card v-for="(pitch, index) in bullpen.bullpen.pitches" :key="index">
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
<ion-card-title>Pitch {{index+1}}</ion-card-title>
|
<ion-card-title>Pitch {{index+1}}</ion-card-title>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
|
|
@ -47,7 +61,7 @@ const gotoHome = () => {
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Pitch Type</ion-label>
|
<ion-label>Pitch Type</ion-label>
|
||||||
<ion-badge>{{pitch.pitchType.name}}</ion-badge>
|
<ion-badge>{{determinePitchTypeName(pitch.pitchTypeId)}}</ion-badge>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Planned Pitch Area</ion-label>
|
<ion-label>Planned Pitch Area</ion-label>
|
||||||
|
|
@ -44,20 +44,20 @@ const gotoFinalizePitch = () => {
|
||||||
currentStep.value = BullpenStep.Finish;
|
currentStep.value = BullpenStep.Finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalizeAndNextPitch = () => {
|
const nextPitch = () => {
|
||||||
store.commit("bullpen/addPitch", pitch.value);
|
storePitch();
|
||||||
pitch.value = BullpenSessionService.createPitch();
|
|
||||||
currentStep.value = BullpenStep.Prepare;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalizeAndEndBullpen = () => {
|
const finishBullpen = () => {
|
||||||
|
storePitch();
|
||||||
|
bullpen.value.bullpen.finishedAt = new Date();
|
||||||
|
router.push({ path: '/summary' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const storePitch = () => {
|
||||||
store.commit("bullpen/addPitch", pitch.value);
|
store.commit("bullpen/addPitch", pitch.value);
|
||||||
pitch.value = BullpenSessionService.createPitch();
|
pitch.value = BullpenSessionService.createPitch();
|
||||||
currentStep.value = BullpenStep.Prepare;
|
currentStep.value = BullpenStep.Prepare;
|
||||||
const bp = bullpen.value.bullpen;
|
|
||||||
bp.finishedAt = new Date();
|
|
||||||
store.dispatch("bullpen/finish", bp);
|
|
||||||
router.push({ name: 'BullpenStats' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setPlannedPitchArea = (hitArea: number) => {
|
const setPlannedPitchArea = (hitArea: number) => {
|
||||||
|
|
@ -96,7 +96,7 @@ const pitchAreaCssClasses = (area: number, name: string) => {
|
||||||
<ion-card-title>Select Pitch type</ion-card-title>
|
<ion-card-title>Select Pitch type</ion-card-title>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
<ion-card-content>
|
<ion-card-content>
|
||||||
<ion-button v-for="(pitchType, index) in pitchTypes" :key="index" :color="pitchType.id !== pitch.pitchTypeId ? 'primary' : 'warning'" @click="setPitchType(pitchType)">
|
<ion-button :disabled="currentStep === BullpenStep.Finish" v-for="(pitchType, index) in pitchTypes" :key="index" :color="pitchType.id !== pitch.pitchTypeId ? 'primary' : 'warning'" @click="setPitchType(pitchType)">
|
||||||
<ion-label>{{pitchType.abbreviation}}</ion-label>
|
<ion-label>{{pitchType.abbreviation}}</ion-label>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
|
|
@ -180,10 +180,10 @@ const pitchAreaCssClasses = (area: number, name: string) => {
|
||||||
<ion-grid>
|
<ion-grid>
|
||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col>
|
<ion-col>
|
||||||
<ion-button color="success" expand="full" @click="finalizeAndNextPitch" :disabled="pitch.hitArea === 0">Save Pitch<br/>&<br/>Next Pitch</ion-button>
|
<ion-button color="success" expand="full" @click="nextPitch" :disabled="pitch.hitArea === 0">Next Pitch</ion-button>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col>
|
<ion-col>
|
||||||
<ion-button color="success" expand="full" @click="finalizeAndEndBullpen" :disabled="pitch.hitArea === 0">Save Pitch<br/>&<br/>End Session</ion-button>
|
<ion-button color="success" expand="full" @click="finishBullpen" :disabled="pitch.hitArea === 0">Finish Session</ion-button>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
IonContent,
|
|
||||||
IonHeader,
|
|
||||||
IonFooter,
|
|
||||||
IonToolbar,
|
|
||||||
IonTitle,
|
|
||||||
IonPage,
|
|
||||||
IonCard,
|
|
||||||
IonCardContent,
|
|
||||||
IonCardHeader,
|
|
||||||
IonCardTitle, IonButton,
|
|
||||||
IonGrid, IonRow, IonCol
|
|
||||||
} from "@ionic/vue";
|
|
||||||
import BullpenSessionService from "@/services/BullpenSessionService";
|
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
import {ref} from 'vue';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const bps = ref(BullpenSessionService);
|
|
||||||
|
|
||||||
const finalizeAndNextPitch = () => {
|
|
||||||
bps.value.nextPitch();
|
|
||||||
router.push({ name: 'PreparePitch' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalizeAndEndBullpen = () => {
|
|
||||||
bps.value.nextPitch();
|
|
||||||
bps.value.finishSession();
|
|
||||||
router.push({ name: 'BullpenStats' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const setRealPitchArea = (hitArea: number) => {
|
|
||||||
bps.value.currentBullpenPitch().realPitchArea = hitArea;
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ion-page>
|
|
||||||
<ion-header :translucent="true">
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title>Finalize Pitch for {{ bps.getPitcher().firstName }} {{ bps.getPitcher().lastName }}</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
<ion-content>
|
|
||||||
<ion-card>
|
|
||||||
<ion-card-header>
|
|
||||||
<ion-card-title>Select Real Pitch Hit Area</ion-card-title>
|
|
||||||
</ion-card-header>
|
|
||||||
<ion-card-content>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 360">
|
|
||||||
<polygon :class="{'wasted-selected': bps.currentBullpenPitch().realPitchArea === 21}" class="wasted" points="0,0 105,0 105,60 60,60 60,105 0,105" @click="setRealPitchArea(21)" />
|
|
||||||
<rect :class="{'wasted-selected': bps.currentBullpenPitch().realPitchArea === 22}" class="wasted" x="105" y="0" width="150" height="60" @click="setRealPitchArea(22)" />
|
|
||||||
<polygon :class="{'wasted-selected': bps.currentBullpenPitch().realPitchArea === 23}" class="wasted" points="255,0 360,0 360,105 300,105 300,60, 255,60" @click="setRealPitchArea(23)" />
|
|
||||||
<rect :class="{'wasted-selected': bps.currentBullpenPitch().realPitchArea === 24}" class="wasted" x="0" y="105" width="60" height="150" @click="setRealPitchArea(24)" />
|
|
||||||
<rect :class="{'wasted-selected': bps.currentBullpenPitch().realPitchArea === 25}" class="wasted" x="300" y="105" width="60" height="150" @click="setRealPitchArea(25)" />
|
|
||||||
<polygon :class="{'wasted-selected': bps.currentBullpenPitch().realPitchArea === 26}" class="wasted" points="0,255 60,255 60,300 105,300 105,360 0,360" @click="setRealPitchArea(26)" />
|
|
||||||
<rect :class="{'wasted-selected': bps.currentBullpenPitch().realPitchArea === 27}" class="wasted" x="105" y="300" width="150" height="60" @click="setRealPitchArea(27)" />
|
|
||||||
<polygon :class="{'wasted-selected': bps.currentBullpenPitch().realPitchArea === 28}" class="wasted" points="255,300 300,300 300,255 360,255 360,360 255,360" @click="setRealPitchArea(28)" />
|
|
||||||
|
|
||||||
<text x="30" y="40" class="number" @click="setRealPitchArea(21)" >21</text>
|
|
||||||
<text x="180" y="40" class="number" @click="setRealPitchArea(22)" >22</text>
|
|
||||||
<text x="330" y="40" class="number" @click="setRealPitchArea(23)" >23</text>
|
|
||||||
<text x="30" y="190" class="number" @click="setRealPitchArea(24)" >24</text>
|
|
||||||
<text x="330" y="190" class="number" @click="setRealPitchArea(25)" >25</text>
|
|
||||||
<text x="30" y="330" class="number" @click="setRealPitchArea(26)" >26</text>
|
|
||||||
<text x="180" y="330" class="number" @click="setRealPitchArea(27)" >27</text>
|
|
||||||
<text x="330" y="330" class="number" @click="setRealPitchArea(28)" >28</text>
|
|
||||||
|
|
||||||
<polygon :class="{'edge-selected': bps.currentBullpenPitch().realPitchArea === 11}" class="edge" points="60,60 155,60 155,105 105,105 105,155, 60,155" @click="setRealPitchArea(11)" />
|
|
||||||
<rect :class="{'edge-selected': bps.currentBullpenPitch().realPitchArea === 12}" class="edge" x="155" y="60" width="50" height="45" @click="setRealPitchArea(12)" />
|
|
||||||
<polygon :class="{'edge-selected': bps.currentBullpenPitch().realPitchArea === 13}" class="edge" points="205,60 300,60 300,155 255,155 255,105, 205,105" @click="setRealPitchArea(13)" />
|
|
||||||
<rect :class="{'edge-selected': bps.currentBullpenPitch().realPitchArea === 14}" class="edge" x="60" y="155" width="45" height="50" @click="setRealPitchArea(14)" />
|
|
||||||
<rect :class="{'edge-selected': bps.currentBullpenPitch().realPitchArea === 15}" class="edge" x="255" y="155" width="45" height="50" @click="setRealPitchArea(15)" />
|
|
||||||
<polygon :class="{'edge-selected': bps.currentBullpenPitch().realPitchArea === 16}" class="edge" points="60,205 105,205 105,255 155,255 155,300, 60,300" @click="setRealPitchArea(16)" />
|
|
||||||
<rect :class="{'edge-selected': bps.currentBullpenPitch().realPitchArea === 17}" class="edge" x="155" y="255" width="50" height="45" @click="setRealPitchArea(17)" />
|
|
||||||
<polygon :class="{'edge-selected': bps.currentBullpenPitch().realPitchArea === 18}" class="edge" points="205,255 255,255 255,205 300,205 300,300 205,300" @click="setRealPitchArea(18)" />
|
|
||||||
|
|
||||||
<text x="80" y="90" class="number" @click="setRealPitchArea(11)">11</text>
|
|
||||||
<text x="180" y="90" class="number" @click="setRealPitchArea(12)">12</text>
|
|
||||||
<text x="275" y="90" class="number" @click="setRealPitchArea(13)">13</text>
|
|
||||||
<text x="80" y="190" class="number" @click="setRealPitchArea(14)">14</text>
|
|
||||||
<text x="275" y="190" class="number" @click="setRealPitchArea(15)">15</text>
|
|
||||||
<text x="80" y="285" class="number" @click="setRealPitchArea(16)">16</text>
|
|
||||||
<text x="180" y="285" class="number" @click="setRealPitchArea(17)">17</text>
|
|
||||||
<text x="275" y="285" class="number" @click="setRealPitchArea(18)">18</text>
|
|
||||||
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 1}" class="inside" x="105" y="105" width="50" height="50" @click="setRealPitchArea(1)"/>
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 2}" class="inside" x="155" y="105" width="50" height="50" @click="setRealPitchArea(2)"/>
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 3}" class="inside" x="205" y="105" width="50" height="50" @click="setRealPitchArea(3)"/>
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 4}" class="inside" x="105" y="155" width="50" height="50" @click="setRealPitchArea(4)"/>
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 5}" class="inside" x="155" y="155" width="50" height="50" @click="setRealPitchArea(5)"/>
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 6}" class="inside" x="205" y="155" width="50" height="50" @click="setRealPitchArea(6)"/>
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 7}" class="inside" x="105" y="205" width="50" height="50" @click="setRealPitchArea(7)"/>
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 8}" class="inside" x="155" y="205" width="50" height="50" @click="setRealPitchArea(8)"/>
|
|
||||||
<rect :class="{'inside-selected': bps.currentBullpenPitch().realPitchArea === 9}" class="inside" x="205" y="205" width="50" height="50" @click="setRealPitchArea(9)"/>
|
|
||||||
|
|
||||||
<text x="130" y="140" class="number" @click="setRealPitchArea(1)">1</text>
|
|
||||||
<text x="180" y="140" class="number" @click="setRealPitchArea(2)">2</text>
|
|
||||||
<text x="230" y="140" class="number" @click="setRealPitchArea(3)">3</text>
|
|
||||||
<text x="130" y="190" class="number" @click="setRealPitchArea(4)">4</text>
|
|
||||||
<text x="180" y="190" class="number" @click="setRealPitchArea(5)">5</text>
|
|
||||||
<text x="230" y="190" class="number" @click="setRealPitchArea(6)">6</text>
|
|
||||||
<text x="130" y="240" class="number" @click="setRealPitchArea(7)">7</text>
|
|
||||||
<text x="180" y="240" class="number" @click="setRealPitchArea(8)">8</text>
|
|
||||||
<text x="230" y="240" class="number" @click="setRealPitchArea(9)">9</text>
|
|
||||||
</svg>
|
|
||||||
</ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
</ion-content>
|
|
||||||
<ion-footer>
|
|
||||||
<ion-grid>
|
|
||||||
<ion-row>
|
|
||||||
<ion-col><ion-button color="success" expand="full" @click="finalizeAndNextPitch" :disabled="bps.currentBullpenPitch().realPitchArea === 0">Save Pitch<br/>&<br/>Next Pitch</ion-button></ion-col>
|
|
||||||
<ion-col><ion-button color="success" expand="full" @click="finalizeAndEndBullpen" :disabled="bps.currentBullpenPitch().realPitchArea === 0">Save Pitch<br/>&<br/>End Session</ion-button></ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
</ion-footer>
|
|
||||||
</ion-page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.wasted {
|
|
||||||
fill: firebrick;
|
|
||||||
stroke: black;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
.wasted-selected {
|
|
||||||
fill: orangered;
|
|
||||||
stroke: black;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
.edge {
|
|
||||||
fill: lightgreen;
|
|
||||||
stroke: black;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
.edge-selected {
|
|
||||||
fill: aquamarine;
|
|
||||||
stroke: black;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
.inside {
|
|
||||||
fill: green;
|
|
||||||
stroke: black;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
.inside-selected {
|
|
||||||
fill: darkolivegreen;
|
|
||||||
stroke: black;
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
.number {
|
|
||||||
text-anchor: middle;
|
|
||||||
fill: white;
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -22,7 +22,7 @@ if (pitcher.value === undefined || pitcher.value === null || pitcher.value === '
|
||||||
|
|
||||||
const startBullpen = () => {
|
const startBullpen = () => {
|
||||||
console.log('Starting bullpen session');
|
console.log('Starting bullpen session');
|
||||||
router.push({ path: '/prepare' });
|
router.push({ path: '/bullpen' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const showStats = () => {
|
const showStats = () => {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { computed, ref, onMounted } from "vue";
|
||||||
import {
|
import {
|
||||||
IonPage,
|
IonPage,
|
||||||
IonIcon,
|
IonIcon,
|
||||||
|
IonLabel,
|
||||||
IonButton,
|
IonButton,
|
||||||
IonContent,
|
IonContent,
|
||||||
IonInput
|
IonInput
|
||||||
|
|
@ -22,6 +23,7 @@ import { useStore } from 'vuex'
|
||||||
import * as yup from 'yup';
|
import * as yup from 'yup';
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const server = JSON.parse(localStorage.getItem("server") || '""');
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
email: yup.string().email('Invalid email address').required('Email is required'),
|
email: yup.string().email('Invalid email address').required('Email is required'),
|
||||||
|
|
@ -29,7 +31,7 @@ const schema = yup.object({
|
||||||
.string()
|
.string()
|
||||||
.min(8, 'Minimum 8 characters')
|
.min(8, 'Minimum 8 characters')
|
||||||
.matches(/\d/, 'Must include a number')
|
.matches(/\d/, 'Must include a number')
|
||||||
.matches(/[^A-Za-z\d]/, 'Must include a special character')
|
// .matches(/[^A-Za-z\d]/, 'Must include a special character')
|
||||||
.required('Password is required'),
|
.required('Password is required'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -63,10 +65,6 @@ const submit = handleSubmit((values, { resetForm }) => {
|
||||||
error => {
|
error => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
console.log(error);
|
console.log(error);
|
||||||
// message.value =
|
|
||||||
// (error.response && error.response.data && error.response.data.message) ||
|
|
||||||
// error.message ||
|
|
||||||
// error.toString();
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}, ({errors}) => {
|
}, ({errors}) => {
|
||||||
|
|
@ -83,6 +81,11 @@ const onLogin = () => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changeServer = () => {
|
||||||
|
router.push({ path: '/setup'});
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -95,6 +98,7 @@ const onLogin = () => {
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
<img src="../assets/Bonn_Capitals_Insignia.png" alt="Logo" class="logo" />
|
<img src="../assets/Bonn_Capitals_Insignia.png" alt="Logo" class="logo" />
|
||||||
</div>
|
</div>
|
||||||
|
<ion-label>{{server.protocol}}://{{server.host}}:{{server.port}}</ion-label>
|
||||||
</div>
|
</div>
|
||||||
<form @submit="submit" class="form-container">
|
<form @submit="submit" class="form-container">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
|
@ -136,9 +140,8 @@ const onLogin = () => {
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ion-button expand="block" class="rounded-button" type="submit">
|
<ion-button expand="block" class="rounded-button" type="submit">Login</ion-button>
|
||||||
Login
|
<ion-button expand="block" class="rounded-button" @click="changeServer">Change Server</ion-button>
|
||||||
</ion-button>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
const goToPreparePitch = (pitcher: User) => {
|
const goToPreparePitch = (pitcher: User) => {
|
||||||
bps.value.startSession( pitcher );
|
bps.value.startSession( pitcher );
|
||||||
router.push({ name: 'PreparePitch' });
|
router.push({ path: '/bullpen' });
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import {ref} from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import {IonButton, IonContent, IonCol, IonHeader, IonInput, IonPage, IonTitle, IonToolbar} from '@ionic/vue';
|
import backendService from '@/services/BackendService'
|
||||||
|
import {IonButton, IonContent, IonCol, IonHeader, IonInput, IonPage, IonTitle, IonToolbar, IonCheckbox} from '@ionic/vue';
|
||||||
|
|
||||||
const serverAddress = ref('');
|
const server = ref(backendService.getServer());
|
||||||
|
|
||||||
|
const useSsl = ref(server.value.protocol === 'https' );
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const saveServerAddress = () => {
|
const saveServerAddress = () => {
|
||||||
localStorage.setItem('serverAddress', serverAddress.value);
|
server.value.protocol = useSsl.value ? 'https' : 'http';
|
||||||
|
backendService.updateServer(server.value);
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -23,8 +27,15 @@ const saveServerAddress = () => {
|
||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col class="ion-padding">
|
<ion-col class="ion-padding">
|
||||||
<ion-input name="ipaddress" v-model="serverAddress" type="text" required placeholder="Enter server address"></ion-input>
|
<ion-input name="ipaddress" v-model="server.host" type="text" required placeholder="Enter server address"></ion-input>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col>
|
||||||
|
<ion-checkbox name="ssl" v-model="useSsl">Use SSL</ion-checkbox>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row>
|
||||||
<ion-col>
|
<ion-col>
|
||||||
<ion-button type="button" fill="solid" expand="full" @click="saveServerAddress">Save</ion-button>
|
<ion-button type="button" fill="solid" expand="full" @click="saveServerAddress">Save</ion-button>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
.idea
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.env.*
|
||||||
|
|
@ -1,20 +1,9 @@
|
||||||
DB_TEST_USER=dev_user
|
DB_USER=dev_user
|
||||||
DB_TEST_PASSWORD=dev_password
|
DB_PASSWORD=dev_password
|
||||||
DB_TEST_NAME=dev_db
|
DB_NAME=dev_db
|
||||||
DB_TEST_HOST=127.0.0.1
|
DB_HOST=127.0.0.1
|
||||||
DB_TEST_PORT=5432
|
DB_PORT=5432
|
||||||
DB_TEST_DIALECT=postgres
|
DB_DIALECT=postgres
|
||||||
|
|
||||||
DB_DEV_USER=dev_user
|
NODE_LOCAL_PORT=8124
|
||||||
DB_DEV_PASSWORD=dev_password
|
NODE_DOCKER_PORT=8080
|
||||||
DB_DEV_NAME=dev_db
|
|
||||||
DB_DEV_HOST=127.0.0.1
|
|
||||||
DB_DEV_PORT=5432
|
|
||||||
DB_DEV_DIALECT=postgres
|
|
||||||
|
|
||||||
DB_PROD_USER=prod_user
|
|
||||||
DB_PROD_PASSWORD=prod_password
|
|
||||||
DB_PROD_NAME=prod_db
|
|
||||||
DB_PROD_HOST=prod-db-server.com
|
|
||||||
DB_PROD_PORT=5432
|
|
||||||
DB_PROD_DIALECT=postgres
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// .sequelizerc
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'config': path.resolve('config/config.js'),
|
||||||
|
'models-path': path.resolve('models'),
|
||||||
|
'seeders-path': path.resolve('seeders'),
|
||||||
|
'migrations-path': path.resolve('migrations')
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
ARG NODE_VERSION=18.0.0
|
||||||
|
FROM node:${NODE_VERSION}-alpine AS base
|
||||||
|
WORKDIR /user/src/app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
FROM base as production
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
COPY .env.production .env
|
||||||
|
RUN --mount=type=bind,source=package.json,target=package.json \
|
||||||
|
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||||
|
--mount=type=cache,target=/root/.npm \
|
||||||
|
npm ci --omit=dev
|
||||||
|
USER node
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["node", "server.js"]
|
||||||
|
|
||||||
|
FROM base as development
|
||||||
|
ENV NODE_ENV=development
|
||||||
|
COPY .env.development .env
|
||||||
|
RUN --mount=type=bind,source=package.json,target=package.json \
|
||||||
|
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||||
|
--mount=type=cache,target=/root/.npm \
|
||||||
|
npm ci --include=dev
|
||||||
|
USER node
|
||||||
|
COPY . .
|
||||||
|
CMD ["npm", "run", "dev"]
|
||||||
|
|
||||||
|
|
||||||
|
#FROM node:20-alpine as build
|
||||||
|
#WORKDIR /app
|
||||||
|
#COPY package*.json ./
|
||||||
|
#RUN npm install --omit dev
|
||||||
|
#COPY . .
|
||||||
|
#
|
||||||
|
#FROM node:20-alpine
|
||||||
|
#WORKDIR /app
|
||||||
|
#COPY --from=build /app /app
|
||||||
|
#ENV NODE_ENV=production
|
||||||
|
#COPY .env.production .env
|
||||||
|
#EXPOSE 8080
|
||||||
|
#CMD ["node", "server.js"]
|
||||||
|
|
@ -20,44 +20,11 @@ app.use(express.urlencoded({ extended: true }));
|
||||||
// initial();
|
// initial();
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// simple route
|
|
||||||
app.get("/", (req, res) => {
|
|
||||||
res.json({ message: "Welcome to bullpen application." });
|
|
||||||
});
|
|
||||||
|
|
||||||
// routes
|
// routes
|
||||||
|
require('./routes/info.routes')(app);
|
||||||
require('./routes/auth.routes')(app);
|
require('./routes/auth.routes')(app);
|
||||||
require('./routes/user.routes')(app);
|
require('./routes/user.routes')(app);
|
||||||
require('./routes/pitchType.routes')(app);
|
require('./routes/pitchType.routes')(app);
|
||||||
require('./routes/bullpenSession.routes')(app);
|
require('./routes/bullpenSession.routes')(app);
|
||||||
|
|
||||||
// function initial() {
|
|
||||||
// Role.bulkCreate([
|
|
||||||
// { name: 'user' },
|
|
||||||
// { name: 'administrator' },
|
|
||||||
// ]);
|
|
||||||
// User.bulkCreate([
|
|
||||||
// { firstName: 'Nolan', lastName: 'Ryan', dateOfBirth: new Date(1947, 1, 31), email: 'ryan.nolan@bullpen.com', password: bcrypt.hashSync('nolan', 8) },
|
|
||||||
// { firstName: 'Sandy', lastName: 'Koufax', dateOfBirth: new Date(1935, 12, 30), email: 'sandy.koufax@bullpen.com', password: bcrypt.hashSync('sandy', 8) },
|
|
||||||
// { firstName: 'Pedro', lastName: 'Martinez', dateOfBirth: new Date(1971, 10, 25), email: 'pedro.martinez@bullpen.com', password: bcrypt.hashSync('pedro', 8) },
|
|
||||||
// { firstName: 'randy', lastName: 'johnson', dateOfBirth: new Date(1963, 9, 10), email: 'randy.johnson@bullpen.com', password: bcrypt.hashSync('randy', 8) }
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// User.findAll().then(users => {
|
|
||||||
// users.forEach(user => {
|
|
||||||
// user.setRoles([1]);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// PitchType.bulkCreate([
|
|
||||||
// { name: 'Fastball', abbreviation: 'FB' },
|
|
||||||
// { name: 'Curveball', abbreviation: 'CB' },
|
|
||||||
// { name: 'Slider', abbreviation: 'SL' },
|
|
||||||
// { name: 'Changeup', abbreviation: 'CH' },
|
|
||||||
// { name: 'Cutter', abbreviation: 'CUT' },
|
|
||||||
// { name: 'Sweeper', abbreviation: 'SW' },
|
|
||||||
// { name: 'Slurve', abbreviation: 'SLV' },
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,23 @@
|
||||||
require('dotenv').config();
|
const dotenv = require('dotenv');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const envFile = {
|
||||||
|
production: '.env.production',
|
||||||
|
test: '.env.test'
|
||||||
|
}[process.env.NODE_ENV || ''] || '.env';
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: path.resolve(process.cwd(), envFile)
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
test: {
|
[process.env.NODE_ENV || 'development']: {
|
||||||
username: process.env.DB_TEST_USER,
|
username: process.env.DB_USER,
|
||||||
password: process.env.DB_TEST_PASSWORD,
|
password: process.env.DB_PASSWORD,
|
||||||
database: process.env.DB_TEST_NAME,
|
database: process.env.DB_NAME,
|
||||||
host: process.env.DB_TEST_HOST,
|
host: process.env.DB_HOST,
|
||||||
port: process.env.DB_TEST_PORT,
|
port: process.env.DB_PORT,
|
||||||
dialect: process.env.DB_TEST_DIALECT,
|
dialect: process.env.DB_DIALECT,
|
||||||
logging: false,
|
logging: false,
|
||||||
},
|
}
|
||||||
development: {
|
|
||||||
username: process.env.DB_DEV_USER,
|
|
||||||
password: process.env.DB_DEV_PASSWORD,
|
|
||||||
database: process.env.DB_DEV_NAME,
|
|
||||||
host: process.env.DB_DEV_HOST,
|
|
||||||
port: process.env.DB_DEV_PORT,
|
|
||||||
dialect: process.env.DB_DEV_DIALECT,
|
|
||||||
logging: false,
|
|
||||||
},
|
|
||||||
production: {
|
|
||||||
username: process.env.DB_PROD_USER,
|
|
||||||
password: process.env.DB_PROD_PASSWORD,
|
|
||||||
database: process.env.DB_PROD_NAME,
|
|
||||||
host: process.env.DB_PROD_HOST,
|
|
||||||
port: process.env.DB_PROD_PORT,
|
|
||||||
dialect: process.env.DB_PROD_DIALECT,
|
|
||||||
logging: false,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,24 @@
|
||||||
const db = require("../models/index");
|
const db = require("../models/index");
|
||||||
const BullpenSession = db.BullpenSession;
|
const BullpenSession = db.BullpenSession;
|
||||||
const Pitch = db.Pitch;
|
const Pitch = db.Pitch;
|
||||||
|
const Op = db.Sequelize.Op;
|
||||||
|
|
||||||
|
const getPagination = (page, size) => {
|
||||||
|
const limit = size ? +size : 3;
|
||||||
|
const offset = page ? page * limit : 0;
|
||||||
|
|
||||||
|
return { limit, offset };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPageData = (data, totalCount, page, limit) => {
|
||||||
|
const currentPage = page ? +page : 0;
|
||||||
|
const totalPages = Math.ceil(totalCount / limit);
|
||||||
|
|
||||||
|
return { totalCount, data, totalPages, currentPage };
|
||||||
|
};
|
||||||
|
|
||||||
exports.insert = (req, res) => {
|
exports.insert = (req, res) => {
|
||||||
BullpenSession.create(req.body.bullpen, { include: [{
|
BullpenSession.create(req.body, { include: [{
|
||||||
model: Pitch,
|
model: Pitch,
|
||||||
as: 'pitches'
|
as: 'pitches'
|
||||||
}]}
|
}]}
|
||||||
|
|
@ -17,20 +32,31 @@ exports.insert = (req, res) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.findAll = (req, res) => {
|
exports.findAll = (req, res) => {
|
||||||
const { user } = req.query;
|
const { page, size, user } = req.query;
|
||||||
const filter = {};
|
const condition = user ? { pitcherId: { [Op.eq]: parseInt(user, 10) } } : null;
|
||||||
if (user) {
|
|
||||||
filter.pitcherId = parseInt(user, 10);
|
const { limit, offset } = getPagination(page, size);
|
||||||
}
|
|
||||||
BullpenSession.findAll({
|
let totalCount = 0;
|
||||||
where: filter,
|
BullpenSession.count({
|
||||||
include: {
|
where: condition,
|
||||||
model: Pitch,
|
limit,
|
||||||
as: 'pitches'
|
offset
|
||||||
}
|
}).then(count => {
|
||||||
|
totalCount = count;
|
||||||
|
return BullpenSession.findAll({
|
||||||
|
where: condition,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
include: {
|
||||||
|
model: Pitch,
|
||||||
|
as: 'pitches'
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.then(bullpenSessions => {
|
.then(bullpenSessions => {
|
||||||
res.status(200).send(bullpenSessions);
|
const response = getPageData(bullpenSessions, totalCount, page, limit);
|
||||||
|
res.status(200).send(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
exports.info = (req, res) => {
|
||||||
|
res.json({
|
||||||
|
message: "Welcome to bullpen",
|
||||||
|
version: process.env.npm_package_version,
|
||||||
|
environment: process.env.NODE_ENV,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file: ./.env.development
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: $DB_NAME
|
||||||
|
POSTGRES_USER: $DB_USER
|
||||||
|
POSTGRES_PASSWORD: $DB_PASSWORD
|
||||||
|
ports:
|
||||||
|
- $DB_PORT:$DB_PORT
|
||||||
|
volumes:
|
||||||
|
- db_data:/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:
|
||||||
|
NODE_ENV: development
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: $DB_PORT
|
||||||
|
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:
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
services:
|
||||||
|
test-db:
|
||||||
|
image: postgres:15
|
||||||
|
ports:
|
||||||
|
- "5433:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: bullpen-test
|
||||||
|
volumes:
|
||||||
|
- /tmp/test-pgdata:/var/lib/postgresql/data
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
container_name: postgres
|
||||||
|
restart: always
|
||||||
|
secrets:
|
||||||
|
- db-password
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: bullpen-prod
|
||||||
|
POSTGRES_USER: test
|
||||||
|
POSTGRES_PASSWORD_FILE: /run/secrets/db-password
|
||||||
|
expose:
|
||||||
|
- 5432
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "pg_isready", "-U", "test", "-d", "bullpen-prod"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: development
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "8124:8080"
|
||||||
|
container_name: bullpen
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
POSTGRES_HOST: db
|
||||||
|
POSTGRES_USER: test
|
||||||
|
POSTGRES_PASSWORD_FILE: /run/secrets/db-password
|
||||||
|
POSTGRES_DB: bullpen-prod
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
secrets:
|
||||||
|
- db-password
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
secrets:
|
||||||
|
db-password:
|
||||||
|
file: db/password.txt
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
// preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
setupFilesAfterEnv: ['./jest.setup.js']
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
const db = require("./models/index");
|
||||||
|
const bcrypt = require("bcryptjs");
|
||||||
|
|
||||||
|
const { beforeAll, afterAll } = require('@jest/globals');
|
||||||
|
|
||||||
|
const { Auth: Auth, User: User, Role: Role, PitchType: PitchType } = db;
|
||||||
|
const Op = db.Sequelize.Op;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await db.sequelize.sync({ force: true }); // Reset DB once for the entire test suite
|
||||||
|
await Role.destroy({ where: {} });
|
||||||
|
await Role.bulkCreate([
|
||||||
|
{ name: 'player' },
|
||||||
|
{ name: 'coach' },
|
||||||
|
{ name: 'admin' }
|
||||||
|
]);
|
||||||
|
const playerRole = await Role.findAll({
|
||||||
|
where: {
|
||||||
|
name: {
|
||||||
|
[Op.eq]: 'player'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Auth.destroy({ where: {} });
|
||||||
|
await User.destroy({ where: {} });
|
||||||
|
await Auth.create({
|
||||||
|
email: 'player@example.com', password: 'hash1234'
|
||||||
|
}).then(auth => {
|
||||||
|
return User.create({
|
||||||
|
firstName: 'Alice',
|
||||||
|
lastName: 'Player',
|
||||||
|
dateOfBirth: '1990-01-01',
|
||||||
|
gender: 'female',
|
||||||
|
handedness: 'right',
|
||||||
|
authId: auth.id
|
||||||
|
});
|
||||||
|
}).then(user => {
|
||||||
|
return user.setRoles(playerRole);
|
||||||
|
});
|
||||||
|
await PitchType.destroy({ where: {} });
|
||||||
|
await PitchType.bulkCreate([
|
||||||
|
{ name: 'Fastball', abbreviation: 'FB' },
|
||||||
|
{ name: 'Curveball', abbreviation: 'CB' },
|
||||||
|
{ name: 'Slider', abbreviation: 'SL' },
|
||||||
|
{ name: 'Changeup', abbreviation: 'CH' },
|
||||||
|
{ name: 'Cutter', abbreviation: 'CUT' },
|
||||||
|
{ name: 'Sweeper', abbreviation: 'SW' },
|
||||||
|
{ name: 'Slurve', abbreviation: 'SLV' }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await db.sequelize.close(); // Close connection after all tests
|
||||||
|
});
|
||||||
|
|
@ -1,18 +1,23 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const process = require('process');
|
|
||||||
const basename = path.basename(__filename);
|
const basename = path.basename(__filename);
|
||||||
|
const dbConfigs = require('../config/config');
|
||||||
const env = process.env.NODE_ENV || 'development';
|
const env = process.env.NODE_ENV || 'development';
|
||||||
const config = require(__dirname + '/../config/config.js')[env];
|
const config = dbConfigs[env];
|
||||||
|
|
||||||
const db = {};
|
const db = {};
|
||||||
|
|
||||||
let sequelize;
|
const sequelize = new Sequelize(
|
||||||
if (config.use_env_variable) {
|
config.database,
|
||||||
sequelize = new Sequelize(process.env[config.use_env_variable], config);
|
config.username,
|
||||||
} else {
|
config.password, {
|
||||||
sequelize = new Sequelize(config.database, config.username, config.password, config);
|
host: config.host,
|
||||||
}
|
port: config.port,
|
||||||
|
dialect: config.dialect,
|
||||||
|
logging: config.logging
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
fs
|
fs
|
||||||
.readdirSync(__dirname)
|
.readdirSync(__dirname)
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,10 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.5.0",
|
||||||
|
"dotenv-cli": "^8.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
"nodemon": "^3.1.9",
|
||||||
"sequelize-cli": "^6.6.2",
|
"sequelize-cli": "^6.6.2",
|
||||||
"supertest": "^7.0.0"
|
"supertest": "^7.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1627,6 +1629,19 @@
|
||||||
"bcrypt": "bin/bcrypt"
|
"bcrypt": "bin/bcrypt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/binary-extensions": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bindings": {
|
"node_modules/bindings": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
|
@ -2020,6 +2035,31 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chokidar": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"anymatch": "~3.1.2",
|
||||||
|
"braces": "~3.0.2",
|
||||||
|
"glob-parent": "~5.1.2",
|
||||||
|
"is-binary-path": "~2.1.0",
|
||||||
|
"is-glob": "~4.0.1",
|
||||||
|
"normalize-path": "~3.0.0",
|
||||||
|
"readdirp": "~3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chownr": {
|
"node_modules/chownr": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||||
|
|
@ -2552,9 +2592,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.7",
|
"version": "16.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
||||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -2564,6 +2604,32 @@
|
||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv-cli": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": "^7.0.6",
|
||||||
|
"dotenv": "^16.3.0",
|
||||||
|
"dotenv-expand": "^10.0.0",
|
||||||
|
"minimist": "^1.2.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"dotenv": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dotenv-expand": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dottie": {
|
"node_modules/dottie": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",
|
||||||
|
|
@ -3461,6 +3527,19 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob-parent": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"is-glob": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob/node_modules/minimatch": {
|
"node_modules/glob/node_modules/minimatch": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
|
|
@ -3684,6 +3763,13 @@
|
||||||
],
|
],
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/ignore-by-default": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/import-local": {
|
"node_modules/import-local": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
||||||
|
|
@ -3794,6 +3880,19 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/is-binary-path": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"binary-extensions": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
|
|
@ -3810,6 +3909,16 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-extglob": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-fullwidth-code-point": {
|
"node_modules/is-fullwidth-code-point": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
|
@ -3830,6 +3939,19 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-glob": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-extglob": "^2.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-lambda": {
|
"node_modules/is-lambda": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
|
||||||
|
|
@ -5633,6 +5755,82 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nodemon": {
|
||||||
|
"version": "3.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
|
||||||
|
"integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chokidar": "^3.5.2",
|
||||||
|
"debug": "^4",
|
||||||
|
"ignore-by-default": "^1.0.1",
|
||||||
|
"minimatch": "^3.1.2",
|
||||||
|
"pstree.remy": "^1.1.8",
|
||||||
|
"semver": "^7.5.3",
|
||||||
|
"simple-update-notifier": "^2.0.0",
|
||||||
|
"supports-color": "^5.5.0",
|
||||||
|
"touch": "^3.1.0",
|
||||||
|
"undefsafe": "^2.0.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nodemon": "bin/nodemon.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/nodemon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nodemon/node_modules/brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nodemon/node_modules/has-flag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nodemon/node_modules/minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nodemon/node_modules/supports-color": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nopt": {
|
"node_modules/nopt": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz",
|
||||||
|
|
@ -6214,6 +6412,13 @@
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pstree.remy": {
|
||||||
|
"version": "1.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||||
|
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pump": {
|
"node_modules/pump": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||||
|
|
@ -6316,6 +6521,19 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/readdirp": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"picomatch": "^2.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
|
@ -6829,6 +7047,19 @@
|
||||||
"simple-concat": "^1.0.0"
|
"simple-concat": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-update-notifier": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.5.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sisteransi": {
|
"node_modules/sisteransi": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
|
|
@ -7419,6 +7650,16 @@
|
||||||
"integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==",
|
"integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/touch": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"nodetouch": "bin/nodetouch.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tunnel-agent": {
|
"node_modules/tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
|
@ -7487,6 +7728,13 @@
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undefsafe": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/underscore": {
|
"node_modules/underscore": {
|
||||||
"version": "1.13.7",
|
"version": "1.13.7",
|
||||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,16 @@
|
||||||
"pretest": "cross-env NODE_ENV=test npm run db:reset",
|
"pretest": "cross-env NODE_ENV=test npm run db:reset",
|
||||||
"db:create:test": "cross-env NODE_ENV=test npx sequelize-cli db:create",
|
"db:create:test": "cross-env NODE_ENV=test npx sequelize-cli db:create",
|
||||||
"db:reset": "npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npx sequelize-cli db:seed:all --debug",
|
"db:reset": "npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npx sequelize-cli db:seed:all --debug",
|
||||||
|
"db:reset:dev": "cross-env NODE_ENV=development npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npm run seed:dev",
|
||||||
|
"db:reset:prod": "cross-env NODE_ENV=production npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npm run seed:prod",
|
||||||
"setup-db": "npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npx sequelize-cli db:seed:all --debug",
|
"setup-db": "npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npx sequelize-cli db:seed:all --debug",
|
||||||
"start": "cross-env NODE_ENV=test node server.js"
|
"seed:dev": "NODE_ENV=development npx sequelize-cli db:seed:all --debug",
|
||||||
|
"start:prod": "npm run cross-env NODE_ENV=production node server.js",
|
||||||
|
"start:dev": "cross-env NODE_ENV=development nodemon server.js",
|
||||||
|
"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:run": "dotenv -e .env.test -- jest --runInBand --detectOpenHandles",
|
||||||
|
"test:full": "npm run test:db:start && npm run test:run && npm run test:db:stop"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
|
|
@ -33,8 +41,10 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.5.0",
|
||||||
|
"dotenv-cli": "^8.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
"nodemon": "^3.1.9",
|
||||||
"sequelize-cli": "^6.6.2",
|
"sequelize-cli": "^6.6.2",
|
||||||
"supertest": "^7.0.0"
|
"supertest": "^7.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Replace with your actual Gitea Docker registry
|
||||||
|
REGISTRY_URL=git.palaeomatiker.home64.de
|
||||||
|
IMAGE_NAME=express-sequelize-app
|
||||||
|
TAG=latest
|
||||||
|
|
||||||
|
# Authenticate with Docker registry
|
||||||
|
echo "Logging into Docker registry..."
|
||||||
|
docker login $REGISTRY_URL
|
||||||
|
|
||||||
|
# Build the image
|
||||||
|
echo "Building Docker image..."
|
||||||
|
docker build -t $IMAGE_NAME .
|
||||||
|
|
||||||
|
# Tag the image
|
||||||
|
echo "Tagging image..."
|
||||||
|
docker tag $IMAGE_NAME $REGISTRY_URL/$IMAGE_NAME:$TAG
|
||||||
|
|
||||||
|
# Push the image
|
||||||
|
echo "Pushing to Gitea registry..."
|
||||||
|
docker push $REGISTRY_URL/$IMAGE_NAME:$TAG
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
const controller = require("../controllers/info.controller");
|
||||||
|
|
||||||
|
module.exports = function(app) {
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
res.header(
|
||||||
|
"Access-Control-Allow-Headers",
|
||||||
|
"x-access-token, Origin, Content-Type, Accept"
|
||||||
|
);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/", controller.info);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
const bcrypt = require("bcryptjs");
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
/** @type {import('sequelize-cli').Migration} */
|
||||||
|
module.exports = {
|
||||||
|
async up (queryInterface, /*Sequelize*/) {
|
||||||
|
if (process.env.NODE_ENV !== 'development') return;
|
||||||
|
|
||||||
|
await queryInterface.bulkInsert('Authentications', [
|
||||||
|
{ email: 'ryan.nolan@bullpen.com', password: bcrypt.hashSync('ryan$123', 8), createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ email: 'sandy.koufax@bullpen.com', password: bcrypt.hashSync('sandy$123', 8), createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ email: 'pedro.martinez@bullpen.com', password: bcrypt.hashSync('pedro$123', 8), createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ email: 'randy.johnson@bullpen.com', password: bcrypt.hashSync('randy$123', 8), createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ email: 'sparky.anderson@bullpen.com', password: bcrypt.hashSync('sparky$123', 8), createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const auths = await queryInterface.select(null, 'Authentications');
|
||||||
|
const ryanAuthId = auths.filter((auth) => auth.email === 'ryan.nolan@bullpen.com').map((auth) => auth.id).shift();
|
||||||
|
const sandyAuthId = auths.filter((auth) => auth.email === 'sandy.koufax@bullpen.com').map((auth) => auth.id).shift();
|
||||||
|
const pedroAuthId = auths.filter((auth) => auth.email === 'pedro.martinez@bullpen.com').map((auth) => auth.id).shift();
|
||||||
|
const randyAuthId = auths.filter((auth) => auth.email === 'randy.johnson@bullpen.com').map((auth) => auth.id).shift();
|
||||||
|
const sparkyAuthId = auths.filter((auth) => auth.email === 'sparky.anderson@bullpen.com').map((auth) => auth.id).shift();
|
||||||
|
|
||||||
|
await queryInterface.bulkInsert('Users', [{
|
||||||
|
firstName: 'Nolan',
|
||||||
|
lastName: 'Ryan',
|
||||||
|
dateOfBirth: new Date(1947, 1, 31),
|
||||||
|
gender: 'male',
|
||||||
|
handedness: 'right',
|
||||||
|
authId: ryanAuthId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}, {
|
||||||
|
firstName: 'Sandy',
|
||||||
|
lastName: 'Koufax',
|
||||||
|
dateOfBirth: new Date(1935, 12, 30),
|
||||||
|
gender: 'male',
|
||||||
|
handedness: 'right',
|
||||||
|
authId: sandyAuthId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}, {
|
||||||
|
firstName: 'Pedro',
|
||||||
|
lastName: 'Martinez',
|
||||||
|
dateOfBirth: new Date(1971, 10, 25),
|
||||||
|
gender: 'male',
|
||||||
|
handedness: 'right',
|
||||||
|
authId: pedroAuthId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}, {
|
||||||
|
firstName: 'randy',
|
||||||
|
lastName: 'johnson',
|
||||||
|
dateOfBirth: new Date(1963, 9, 10),
|
||||||
|
gender: 'male',
|
||||||
|
handedness: 'right',
|
||||||
|
authId: randyAuthId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}, {
|
||||||
|
firstName: 'Sparky',
|
||||||
|
lastName: 'Anderson',
|
||||||
|
dateOfBirth: new Date(1934, 22, 2),
|
||||||
|
gender: 'male',
|
||||||
|
authId: sparkyAuthId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
}]);
|
||||||
|
|
||||||
|
const users = await queryInterface.select(null, 'Users');
|
||||||
|
const ryanId = users.filter((user) => user.firstName === 'Ryan').map((user) => user.id).shift();
|
||||||
|
const sandyId = users.filter((user) => user.firstName === 'Sandy').map((user) => user.id).shift();
|
||||||
|
const pedroId = users.filter((user) => user.firstName === 'Pedro').map((user) => user.id).shift();
|
||||||
|
const randyId = users.filter((user) => user.firstName === 'Randy').map((user) => user.id).shift();
|
||||||
|
const sparkyId = users.filter((user) => user.firstName === 'Sparky').map((user) => user.id).shift();
|
||||||
|
|
||||||
|
const roles = await queryInterface.select(null, 'Roles');
|
||||||
|
const playerId = roles.filter((role) => role.name === 'player').map((role) => role.id).shift();
|
||||||
|
const coachId = roles.filter((role) => role.name === 'coach').map((role) => role.id).shift();
|
||||||
|
const adminId = roles.filter((role) => role.name === 'admin').map((role) => role.id).shift();
|
||||||
|
|
||||||
|
await queryInterface.bulkInsert('UserRoles', [
|
||||||
|
{ userId: ryanId, roleId: playerId, createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ userId: sandyId, roleId: playerId, createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ userId: pedroId, roleId: playerId, createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ userId: randyId, roleId: playerId, createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ userId: sparkyId, roleId: coachId, createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
{ userId: sparkyId, roleId: adminId, createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
async down (queryInterface, /*Sequelize*/) {
|
||||||
|
if (process.env.NODE_ENV !== 'development') return;
|
||||||
|
|
||||||
|
await queryInterface.dropTable('Authentications');
|
||||||
|
await queryInterface.dropTable('Users');
|
||||||
|
await queryInterface.dropTable('UserRoles');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
const bcrypt = require("bcryptjs");
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
/** @type {import('sequelize-cli').Migration} */
|
||||||
|
module.exports = {
|
||||||
|
async up (queryInterface, /*Sequelize*/) {
|
||||||
|
if (process.env.NODE_ENV !== 'production') return;
|
||||||
|
|
||||||
|
await queryInterface.bulkInsert('Authentications', [
|
||||||
|
{ email: 'admin@example.com', password: bcrypt.hashSync('admin$123', 8), createdAt: new Date(), updatedAt: new Date() }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const auths = await queryInterface.select(null, 'Authentications');
|
||||||
|
const adminAuthId = auths.filter((auth) => auth.email === 'admin@example.com').map((auth) => auth.id).shift();
|
||||||
|
|
||||||
|
await queryInterface.bulkInsert('Users', [{
|
||||||
|
firstName: 'Admin',
|
||||||
|
lastName: 'Bullpen',
|
||||||
|
dateOfBirth: '1970-01-01',
|
||||||
|
gender: 'other',
|
||||||
|
authId: adminAuthId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|
||||||
|
const users = await queryInterface.select(null, 'Users');
|
||||||
|
const adminUserId = users.filter((user) => user.firstName === 'Admin').map((user) => user.id).shift();
|
||||||
|
|
||||||
|
const roles = await queryInterface.select(null, 'Roles');
|
||||||
|
const adminRoleId = roles.filter((role) => role.name === 'admin').map((role) => role.id).shift();
|
||||||
|
|
||||||
|
await queryInterface.bulkInsert('UserRoles', [
|
||||||
|
{ userId: adminUserId, roleId: adminRoleId, createdAt: new Date(), updatedAt: new Date() }
|
||||||
|
]);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async down (queryInterface, /*Sequelize*/) {
|
||||||
|
if (process.env.NODE_ENV !== 'production') return;
|
||||||
|
|
||||||
|
await queryInterface.dropTable('Authentications');
|
||||||
|
await queryInterface.dropTable('Users');
|
||||||
|
await queryInterface.dropTable('UserRoles');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
const bcrypt = require("bcryptjs");
|
const bcrypt = require("bcryptjs");
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
/** @type {import('sequelize-cli').Migration} */
|
/** @type {import('sequelize-cli').Migration} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async up (queryInterface, /*Sequelize*/) {
|
async up (queryInterface, /*Sequelize*/) {
|
||||||
|
if (process.env.NODE_ENV !== 'test') return;
|
||||||
|
|
||||||
await queryInterface.bulkInsert('Authentications', [
|
await queryInterface.bulkInsert('Authentications', [
|
||||||
{ email: 'player@example.com', password: bcrypt.hashSync('hash1234', 8), createdAt: new Date(), updatedAt: new Date() },
|
{ email: 'player@example.com', password: bcrypt.hashSync('hash1234', 8), createdAt: new Date(), updatedAt: new Date() },
|
||||||
{ email: 'coach@example.com', password: bcrypt.hashSync('hash2345', 8), createdAt: new Date(), updatedAt: new Date() },
|
{ email: 'coach@example.com', password: bcrypt.hashSync('hash2345', 8), createdAt: new Date(), updatedAt: new Date() },
|
||||||
|
|
@ -15,33 +18,33 @@ module.exports = {
|
||||||
const adminAuthId = auths.filter((auth) => auth.email === 'admin@example.com').map((auth) => auth.id).shift();
|
const adminAuthId = auths.filter((auth) => auth.email === 'admin@example.com').map((auth) => auth.id).shift();
|
||||||
|
|
||||||
await queryInterface.bulkInsert('Users', [{
|
await queryInterface.bulkInsert('Users', [{
|
||||||
firstName: 'Alice',
|
firstName: 'Alice',
|
||||||
lastName: 'Player',
|
lastName: 'Player',
|
||||||
dateOfBirth: '1990-01-01',
|
dateOfBirth: '1990-01-01',
|
||||||
gender: 'female',
|
gender: 'female',
|
||||||
handedness: 'right',
|
handedness: 'right',
|
||||||
authId: playerAuthId,
|
authId: playerAuthId,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
}, {
|
}, {
|
||||||
firstName: 'Bob',
|
firstName: 'Bob',
|
||||||
lastName: 'Coach',
|
lastName: 'Coach',
|
||||||
dateOfBirth: '1985-05-05',
|
dateOfBirth: '1985-05-05',
|
||||||
gender: 'male',
|
gender: 'male',
|
||||||
handedness: 'left',
|
handedness: 'left',
|
||||||
authId: coachAuthId,
|
authId: coachAuthId,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
}, {
|
}, {
|
||||||
firstName: 'Charlie',
|
firstName: 'Charlie',
|
||||||
lastName: 'Admin',
|
lastName: 'Admin',
|
||||||
dateOfBirth: '1980-03-03',
|
dateOfBirth: '1980-03-03',
|
||||||
gender: 'other',
|
gender: 'other',
|
||||||
handedness: 'both',
|
handedness: 'both',
|
||||||
authId: adminAuthId,
|
authId: adminAuthId,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
const users = await queryInterface.select(null, 'Users');
|
const users = await queryInterface.select(null, 'Users');
|
||||||
|
|
@ -59,10 +62,11 @@ module.exports = {
|
||||||
{ userId: bobId, roleId: coachId, createdAt: new Date(), updatedAt: new Date() },
|
{ userId: bobId, roleId: coachId, createdAt: new Date(), updatedAt: new Date() },
|
||||||
{ userId: charlieId, roleId: adminId, createdAt: new Date(), updatedAt: new Date() },
|
{ userId: charlieId, roleId: adminId, createdAt: new Date(), updatedAt: new Date() },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async down (queryInterface, /*Sequelize*/) {
|
async down (queryInterface, /*Sequelize*/) {
|
||||||
|
if (process.env.NODE_ENV !== 'test') return;
|
||||||
|
|
||||||
await queryInterface.dropTable('Authentications');
|
await queryInterface.dropTable('Authentications');
|
||||||
await queryInterface.dropTable('Users');
|
await queryInterface.dropTable('Users');
|
||||||
await queryInterface.dropTable('UserRoles');
|
await queryInterface.dropTable('UserRoles');
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
const app = require('./app');
|
const app = require('./app');
|
||||||
|
require('dotenv').config({
|
||||||
|
path: process.env.NODE_ENV === 'production' ? '.env.production' : '.env'
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbConfigs = require('./config/config');
|
||||||
|
const env = process.env.NODE_ENV || 'development';
|
||||||
|
const config = dbConfigs[env];
|
||||||
|
|
||||||
// set port, listen for requests
|
// set port, listen for requests
|
||||||
const PORT = process.env.PORT || 8080;
|
const PORT = process.env.PORT || 8080;
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server is running on port ${PORT}.`);
|
console.log(`Server is running on port ${PORT} in ${process.env.NODE_ENV} mode.`);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,14 @@ const app = require("../app")
|
||||||
|
|
||||||
const { bullpenSession } = require("./data/bullpenSession.test.data")
|
const { bullpenSession } = require("./data/bullpenSession.test.data")
|
||||||
|
|
||||||
|
const validateBullpenSession = (bullpenSession) => {
|
||||||
|
expect(bullpenSession.id).toBeDefined();
|
||||||
|
expect(bullpenSession.id).toBeGreaterThan(0);
|
||||||
|
expect(bullpenSession.pitches).toBeDefined();
|
||||||
|
expect(Array.isArray(bullpenSession.pitches)).toBe(true);
|
||||||
|
expect(bullpenSession.pitches.length).toBe(2);
|
||||||
|
}
|
||||||
|
|
||||||
describe("Test bullpen session", () => {
|
describe("Test bullpen session", () => {
|
||||||
test("should create bullpen session with pitches", async () => {
|
test("should create bullpen session with pitches", async () => {
|
||||||
let response = await request(app)
|
let response = await request(app)
|
||||||
|
|
@ -27,14 +35,10 @@ describe("Test bullpen session", () => {
|
||||||
expect(response.statusCode).toBe(201);
|
expect(response.statusCode).toBe(201);
|
||||||
const bullpenSessionData = await response.body;
|
const bullpenSessionData = await response.body;
|
||||||
expect(bullpenSessionData).toBeDefined();
|
expect(bullpenSessionData).toBeDefined();
|
||||||
expect(bullpenSessionData.id).toBeDefined();
|
validateBullpenSession(bullpenSessionData);
|
||||||
expect(bullpenSessionData.id).toBeGreaterThan(0);
|
|
||||||
expect(bullpenSessionData.pitches).toBeDefined();
|
|
||||||
expect(Array.isArray(bullpenSessionData.pitches)).toBe(true);
|
|
||||||
expect(bullpenSessionData.pitches.length).toBe(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip("should get all bullpen sessions", async () => {
|
test("should get all bullpen sessions", async () => {
|
||||||
let response = await request(app)
|
let response = await request(app)
|
||||||
.post("/api/auth/login")
|
.post("/api/auth/login")
|
||||||
.send({
|
.send({
|
||||||
|
|
@ -48,16 +52,15 @@ describe("Test bullpen session", () => {
|
||||||
.set('x-access-token', user.accessToken);
|
.set('x-access-token', user.accessToken);
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
const bullpenSessionDataArray = await response.body;
|
const { totalCount, data, totalPages, currentPage } = await response.body;
|
||||||
expect(bullpenSessionDataArray).toBeDefined();
|
expect(data).toBeDefined();
|
||||||
expect(Array.isArray(bullpenSessionDataArray)).toBe(true);
|
expect(Array.isArray(data)).toBe(true);
|
||||||
expect(bullpenSessionDataArray.length).toBe(1);
|
expect(data.length).toBe(1);
|
||||||
const bullpenSessionData = bullpenSessionDataArray[0];
|
expect(totalCount).toBe(1);
|
||||||
expect(bullpenSessionData.id).toBeDefined();
|
expect(totalPages).toBe(1);
|
||||||
expect(bullpenSessionData.id).toBeGreaterThan(0);
|
expect(currentPage).toBe(0);
|
||||||
expect(bullpenSessionData.pitches).toBeDefined();
|
const bullpenSessionData = data[0];
|
||||||
expect(Array.isArray(bullpenSessionData.pitches)).toBe(true);
|
validateBullpenSession(bullpenSessionData);
|
||||||
expect(bullpenSessionData.pitches.length).toBe(2);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should get all bullpen sessions for user", async () => {
|
test("should get all bullpen sessions for user", async () => {
|
||||||
|
|
@ -75,15 +78,14 @@ describe("Test bullpen session", () => {
|
||||||
.query({ user: user.id });
|
.query({ user: user.id });
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
const bullpenSessionDataArray = await response.body;
|
const { totalCount, data, totalPages, currentPage } = await response.body;
|
||||||
expect(bullpenSessionDataArray).toBeDefined();
|
expect(data).toBeDefined();
|
||||||
expect(Array.isArray(bullpenSessionDataArray)).toBe(true);
|
expect(Array.isArray(data)).toBe(true);
|
||||||
expect(bullpenSessionDataArray.length).toBe(1);
|
expect(data.length).toBe(1);
|
||||||
const bullpenSessionData = bullpenSessionDataArray[0];
|
expect(totalCount).toBe(1);
|
||||||
expect(bullpenSessionData.id).toBeDefined();
|
expect(totalPages).toBe(1);
|
||||||
expect(bullpenSessionData.id).toBeGreaterThan(0);
|
expect(currentPage).toBe(0);
|
||||||
expect(bullpenSessionData.pitches).toBeDefined();
|
const bullpenSessionData = data[0];
|
||||||
expect(Array.isArray(bullpenSessionData.pitches)).toBe(true);
|
validateBullpenSession(bullpenSessionData);
|
||||||
expect(bullpenSessionData.pitches.length).toBe(2);
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const request = require("supertest")
|
||||||
const {
|
const {
|
||||||
expect,
|
expect,
|
||||||
describe,
|
describe,
|
||||||
test,
|
test, beforeAll, afterAll,
|
||||||
} = require('@jest/globals');
|
} = require('@jest/globals');
|
||||||
|
|
||||||
const app = require("../app")
|
const app = require("../app")
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ describe("Test user authentication", () => {
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Test user login", async() => {
|
test("should login user", async() => {
|
||||||
let response = await request(app)
|
let response = await request(app)
|
||||||
.post("/api/auth/login")
|
.post("/api/auth/login")
|
||||||
.send({
|
.send({
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue