Tomi Cvetic 6 роки тому
батько
коміт
fe1717ef25

+ 1 - 0
.gitignore

@@ -10,6 +10,7 @@ node_modules/
 /client/build
 /data
 /server/swisstennis_files
+/server/sztm_files
 
 # misc
 .DS_Store

Різницю між файлами не показано, бо вона завелика
+ 303 - 230
server/package-lock.json


+ 2 - 0
server/package.json

@@ -3,6 +3,7 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "await-fs": "^1.0.0",
     "babel-core": "^6.25.0",
     "babel-polyfill": "^6.23.0",
     "babel-preset-env": "^1.7.0",
@@ -21,6 +22,7 @@
     "moment": "^2.18.1",
     "mongoose": "^5.1.1",
     "morgan": "^1.8.2",
+    "pdfmake": "^0.1.37",
     "request": "^2.81.0",
     "request-promise": "^4.2.1",
     "xlsx": "^0.10.4"

+ 6 - 12
server/src/excel.js → server/src/restServer/excel.js

@@ -56,18 +56,12 @@ function saveAs (workbook, filename) {
 
 function readWorkbook (file) {
   return new Promise((resolve, reject) => {
-    const reader = new FileReader()
-    reader.onload = (e) => {
-      const data = e.target.result
-      const workbook = XLSX.read(data, {type: 'binary', cellDates: true})
-      console.log('Workbook after read:', workbook)
-      const worksheets = {}
-      Object.keys(workbook.Sheets).forEach(sheetName => {
-        worksheets[sheetName] = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName], { header: 1, raw: true })
-      })
-      resolve(worksheets)
-    }
-    reader.readAsBinaryString(file)
+    const workbook = XLSX.readFile(file, {type: 'binary', cellDates: true})
+    const worksheets = {}
+    Object.keys(workbook.Sheets).forEach(sheetName => {
+      worksheets[sheetName] = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName], { header: 1, raw: true })
+    })
+    resolve(worksheets)
   })
 }
 

+ 74 - 0
server/src/restServer/helpers.js

@@ -0,0 +1,74 @@
+import moment from 'moment'
+
+function date2s (date) {
+  return moment(date).format('DD.MM.')
+}
+
+function time2s (date) {
+  return moment(date).format('HH:mm')
+}
+
+function datetime2s (date) {
+  return moment(date).format('DD.MM. HH:mm')
+}
+
+function normalize (item, type) {
+  return item ? String(item).replace(/\s+/g, ' ').trim() : null
+}
+
+function fileSize (int) {
+  let value
+  if (int > Math.pow(2, 10)) {
+    value = `${int / Math.pow(2, 10)}kB`
+  }
+  if (int > Math.pow(2, 20)) {
+    value = `${int / Math.pow(2, 20)}MB`
+  }
+  if (int > Math.pow(2, 30)) {
+    value = `${int / Math.pow(2, 30)}GB`
+  }
+  return value
+}
+
+function normalizePhone (item) {
+  let phone = String(item).replace(/\s|\+|\/|,|-|'/g, '').replace(/\(0\)/, '').replace(/^0+/, '')
+  if (phone.match(/[^\d*]/)) {
+    return `FEHLER (nicht-numerische Zeichen): ${phone}`
+  }
+  if (phone.length === 0) {
+    return null
+  } else if (phone.length === 9) {
+    // Assume swiss number
+    phone = `+41${phone}`
+  } else if (phone.length === 11) {
+    phone = `+${phone}`
+  } else {
+    return `FEHLER (falsche Länge): ${phone}`
+  }
+
+  return phone
+}
+
+function filterFuzzy (item, filter) {
+  const lowerCaseItem = item.toLowerCase()
+  const substrings = filter.toLowerCase().split(/\s+/)
+  let lastIndex = 0
+  const indices = substrings.map(substring => {
+    const index = lowerCaseItem.subString(lastIndex).indexOf(substring)
+    if (index === -1) {
+      return null
+    } else {
+      const startIndex = lastIndex + index
+      const endIndex = startIndex + substring.length
+      lastIndex = endIndex
+      return {startIndex, endIndex}
+    }
+  })
+  return indices
+}
+
+function filterExact (item, filter) {
+  return item === filter
+}
+
+export { date2s, time2s, datetime2s, normalize, normalizePhone, fileSize, filterFuzzy, filterExact }

+ 13 - 0
server/src/restServer/models/file.js

@@ -0,0 +1,13 @@
+import mongoose from 'mongoose'
+
+const SwissTennisFileSchema = new mongoose.Schema({
+  downloaded: Date,
+  name: String,
+  type: Date,
+  size: String,
+  path: String
+})
+
+const SwissTennisFile = mongoose.model('SwissTennisFile', SwissTennisFileSchema)
+
+export default SwissTennisFile

+ 16 - 0
server/src/restServer/models/match.js

@@ -0,0 +1,16 @@
+import mongoose from 'mongoose'
+
+const MatchSchema = new mongoose.Schema({
+  _created: Date,
+  idString: String,
+  date: Date,
+  category: String,
+  player1: { type: mongoose.Schema.Types.ObjectId, ref: 'Player' },
+  player2: { type: mongoose.Schema.Types.ObjectId, ref: 'Player' },
+  result: String,
+  doubles: Boolean
+})
+
+const Match = mongoose.model('Match', MatchSchema)
+
+export default Match

+ 12 - 0
server/src/restServer/models/matchList.js

@@ -0,0 +1,12 @@
+import mongoose from 'mongoose'
+
+const MatchListSchema = new mongoose.Schema({
+    imported: Date,
+    file: String,
+    fileSize: Number,
+    matches: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Match' }]
+})
+
+const MatchList = mongoose.model('MatchList', MatchListSchema)
+
+export default MatchList

+ 26 - 92
server/src/restServer/models/player.js

@@ -1,99 +1,33 @@
 import mongoose from 'mongoose'
-import bcrypt from 'bcrypt'
 
 const PlayerSchema = new mongoose.Schema({
-  name: {
-    type: String,
-    unique: true,
-    required: true
-  },
-  firstName: {
-    type: String,
-    required: true
-  },
-  licenseNr: {
-    type: String
-  },
-  birthDate: {
-    type: String,
-    required: true
-  },
-  gender: {
-    type: String,
-    required: true
-  },
-  phonePrivate: {
-    type: String,
-    required: true
-  },
-  phoneWork: {
-    type: String,
-    required: true
-  },
-  phoneMobile: {
-    type: String,
-    required: true
-  },
-  email: {
-    type: String,
-    required: true
-  },
-  ranking: {
-    type: String,
-    required: true
-  },
-  nameDP: {
-    type: String,
-    unique: true,
-    required: true
-  },
-  firstNameDP: {
-    type: String,
-    required: true
-  },
-  licenseNrDP: {
-    type: String
-  },
-  birthDateDP: {
-    type: String,
-    required: true
-  },
-  rankingDP: {
-    type: String,
-    required: true
-  },
-  confirmed: {
-    type: Boolean,
-    required: true
-  },
-  payed: {
-    type: Date
-  },
-  matches: {
-    type: [String]
-  }
-})
+  created: Date,
+
+  category: String,
+  licenseNr: String,
+  gender: String,
+  name: String,
+  firstName: String,
+  birthDate: Date,
+  phonePrivate: String,
+  phoneWork: String,
+  phoneMobile: String,
+  email: String,
+  ranking: String,
+  licenseNrDP: String,
+  nameDP: String,
+  firstNameDP: String,
+  birthDateDP: String,
+  rankingDP: String,
+  confirmed: Boolean,
+  paid: Boolean,
 
-    this.Konkurrenz = normalize(data[0])
-    this.Matches = []
-    this.isDoubles = !!this.Konkurrenz.match(/DM.*|[MW]D.*/)
-    this.isJunior = (this.Geburtsdatum) ? this.Geburtsdatum.getTime() >= (new Date((new Date()).getFullYear() - 19, 11, 31, 23, 59, 59, 999)).getTime() : false
-    this.isJuniorDP = (this.isDoubles && this.GeburtsdatumDP) ? this.GeburtsdatumDP.getTime() >= (new Date((new Date()).getFullYear() - 19, 11, 31, 23, 59, 59, 999)).getTime() : false
-    this.name = this.isDoubles ? `${this.Name} ${this.Vorname} / ${this.NameDP} ${this.VornameDP}` : `${this.Name} ${this.Vorname}`
-    if (this.Mobile && this.Mobile.match(reMobile)) {
-      this.phone = this.Mobile
-    } else if (this.TelP && this.TelP.match(reMobile)) {
-      this.phone = this.TelP
-    } else if (this.TelG && this.TelG.match(reMobile)) {
-      this.phone = this.TelG
-    } else if (this.Mobile && this.Mobile.match(/FEHLER/)) {
-      this.phone = this.Mobile
-    } else if (this.TelP && this.TelP.match(/FEHLER/)) {
-      this.phone = this.TelP
-    } else if (this.TelG && this.TelG.match(/FEHLER/)) {
-      this.phone = this.TelG
-    }
-  }
+  doubles: Boolean,
+  junior: Boolean,
+  fullName: String,
+  phone: String,
+  idString: String,
+})
 
 const Player = mongoose.model('Player', PlayerSchema)
 

+ 12 - 0
server/src/restServer/models/playerList.js

@@ -0,0 +1,12 @@
+import mongoose from 'mongoose'
+
+const PlayerListSchema = new mongoose.Schema({
+    imported: Date,
+    file: String,
+    fileSize: Number,
+    players: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Player' }]
+})
+
+const PlayerList = mongoose.model('PlayerList', PlayerListSchema)
+
+export default PlayerList

+ 3 - 13
server/src/restServer/models/user.js

@@ -3,19 +3,9 @@ import bcrypt from 'bcrypt'
 
 // Define the user schema
 const UserSchema = new mongoose.Schema({
-  username: {
-    type: String,
-    unique: true,
-    required: true
-  },
-  name: {
-    type: String,
-    required: true
-  },
-  password: {
-    type: String,
-    required: true
-  }
+  username: String,
+  name: String,
+  password: String
 })
 
 // Before saving the user to the database, hash the password.

+ 200 - 119
server/src/restServer/routes/swisstennis.js

@@ -1,7 +1,13 @@
 import fs from 'fs'
+import awaitFs from 'await-fs'
 import express from 'express'
 import bhttp from 'bhttp'
-import excel from '../../excel'
+import Excel from '../excel'
+import Player from '../models/player'
+import Match from '../models/match'
+import PlayerList from '../models/playerList'
+import MatchList from '../models/matchList'
+import { normalize, normalizePhone } from '../helpers'
 
 // Create the router
 const swisstennis = express.Router()
@@ -14,22 +20,18 @@ function fileDate (date) {
 }
 
 /*
- * Define the Routes
- */
+* Define the Routes
+*/
 
 // Login
 swisstennis.post('/login', async (req, res) => {
   const { username, password } = req.body
-
+  
   // return, if username or password are missing
   if (!username || !password) {
-    res.status(400).json({
-      success: false,
-      msg: 'Parameters username and password are required'
-    })
-    return
+    return res.status(400).json({ msg: 'Parameters username and password are required' })
   }
-
+  
   // assemble the login data
   const loginData = {
     Lang: 'D',
@@ -37,31 +39,21 @@ swisstennis.post('/login', async (req, res) => {
     pwd: password,
     Tournament: ''
   }
-
+  
   try {
     const myTournamentsPage = await session.get('https://comp.swisstennis.ch/advantage/servlet/MyTournamentList?Lang=D')
     const loginPage = await session.post('https://comp.swisstennis.ch/advantage/servlet/Login', loginData)
     const strLoginPage = loginPage.body.toString()
     if (strLoginPage.includes('Zugriff verweigert')) {
-      res.status(400).json({
-        success: false,
-        msg: 'Access denied!'
-      })
-      return
+      return res.status(400).json({ msg: 'Access denied!' })
     }
   } catch (error) {
-    res.status(400).json({
-      success: false,
-      msg: error
-    })
-    return
+    return res.status(400).json({ msg: error })
   }
-  res.json({
-    success: true,
-    msg: 'Logged in successfully.'
-  })
+  res.json({ msg: 'Logged in successfully.' })
 })
 
+// Overview of tournaments
 swisstennis.get('/tournaments', async (req, res) => {
   const tournaments = {}
   try {
@@ -69,14 +61,10 @@ swisstennis.get('/tournaments', async (req, res) => {
     const myTournamentsPage = await session.get('https://comp.swisstennis.ch/advantage/servlet/MyTournamentList?Lang=D')
     const strMyTournamentsPage = myTournamentsPage.body.toString()
     if (strMyTournamentsPage.includes('Login-Zone')) {
-      res.status(400).json({
-        success: false,
-        message: 'Not logged in.'
-      })
-      return
+      return res.status(400).json({ message: 'Not logged in.' })
     }
     const tournamentRegexp = /<a href=".*ProtectedDisplayTournament.*tournament=Id(\d+)">([^<]+)<\/a>/gm
-
+    
     do {
       match = tournamentRegexp.exec(strMyTournamentsPage)
       if (match) {
@@ -84,27 +72,17 @@ swisstennis.get('/tournaments', async (req, res) => {
       }
     } while (match)
   } catch (error) {
-    res.status(400).json({
-      success: false,
-      msg: error.toString()
-    })
-    return
+    return res.status(400).json({ msg: error.toString() })
   }
-  res.json({
-    success: true,
-    tournaments
-  })
+  res.json({ tournaments })
 })
 
+// Draws of a tournament
 swisstennis.get('/draws/:tournament', async (req, res) => {
   const { tournament } = req.params
-
+  
   if (!tournament) {
-    res.json({
-      success: false,
-      msg: 'No tournament given.'
-    })
-    return
+    return res.json({ msg: 'No tournament given.' })
   }
   try {
     let match
@@ -112,127 +90,230 @@ swisstennis.get('/draws/:tournament', async (req, res) => {
     const tournamentPage = await session.get(`https://comp.swisstennis.ch/advantage/servlet/ProtectedDisplayTournament?Lang=D&tournament=Id${tournament}`)
     const strTournamentPage = tournamentPage.body.toString()
     if (strTournamentPage.includes('Login-Zone')) {
-      res.status(400).json({
-        success: false,
-        message: 'Not logged in.'
-      })
-      return
+      return res.status(400).json({ message: 'Not logged in.' })
     }
     const drawRegexp = /<a (?:class="text" )?href=".*DisplayEvent.*eventId=(\d+).*">([^<]+)<\/a>/gm
-
+    
     do {
       match = drawRegexp.exec(strTournamentPage)
       if (match) {
         draws[match[1]] = match[2]
       }
     } while (match)
-    res.json({
-      success: true,
-      draws
-    })
+    return res.json({ draws })
   } catch (error) {
-    res.status(400).json({
-      success: false,
-      msg: error.toString()
-    })
+    return res.status(400).json({ msg: error.toString() })
   }
 })
 
-swisstennis.get('/download/playerlist/:tournament', async (req, res) => {
+// Download a playerlist
+swisstennis.get('/playerlist/download/:tournament', async (req, res) => {
   const { tournament } = req.params
-
+  
   if (!tournament) {
-    res.json({
-      success: false,
-      msg: 'No tournament given.'
-    })
-    return
+    return res.json({ msg: 'No tournament given.' })
   }
   try {
     const playerListFile = fs.createWriteStream(`swisstennis_files/PlayerList-${fileDate(new Date())}.xls`)
     const playerList = await session.get(`https://comp.swisstennis.ch/advantage/servlet/PlayerList.xls?tournament=Id${tournament}&lang=D`, {stream: true})
     playerList.pipe(playerListFile)
-    res.json({
-      success: true,
-      msg: 'Download successful.'
-    })
+    return res.json({ msg: 'Download successful.' })
   } catch (error) {
-    res.status(400).json({
-      success: false,
-      msg: error.toString()
+    return res.status(400).json({ msg: error.toString() })
+  }
+})
+
+swisstennis.get('/playerlist/parse/:filename', async (req, res) => {
+  try {
+    console.log('Parsing file', req.params.filename)
+    
+    const filePath = `swisstennis_files/${req.params.filename}`
+    const dateElems = req.params.filename.match(/\w+\-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.xls/).slice(1,7)
+    const fileDate = new Date(`${dateElems[0]}-${dateElems[1]}-${dateElems[2]} ${dateElems[3]}:${dateElems[4]}:${dateElems[5]}`)
+    const stat = fs.statSync(filePath)
+
+    console.log('About to read the player list.')
+    const worksheets = await Excel.readWorkbook(filePath)
+    const worksheet = worksheets.Players
+    if (worksheet[4].length !== 32 & worksheet[3][0] !== 'Konkurrenz' & worksheet[3][31] !== 'bezahlt') {
+      throw Error(`Wrong file structure. Length: ${worksheet[4].length} (expected 32), Column A name: ${worksheet[3][0]} (expected Konkurrenz).`)
+    }
+
+    const headers = worksheet.slice(3, 4)
+
+    const dbPlayers = await Player.find().lean()
+    console.log(dbPlayers.slice(0,3))
+
+    const allPlayers = worksheet.slice(4, worksheet.length).map(data => {
+      const filePlayer = {
+        created: new Date(),
+        category: normalize(data[0]),
+        licenseNr: normalize(data[2]),
+        name: normalize(data[5]),
+        firstName: normalize(data[6]), 
+        nameDP: normalize(data[24]),
+        firstNameDP: normalize(data[25]),
+        birthDate: data[7],
+        email: normalize(data[16]),
+        ranking: normalize(data[17]),
+        licenseNrDP: normalize(data[23]),
+        phonePrivate: normalizePhone(data[13]),
+        phoneWork: normalizePhone(data[14]),
+        phoneMobile: normalizePhone(data[15]),
+        birthDateDP: data[26],
+        rankingDP: normalize(data[27]),
+        confirmed: data[29],
+        paid: data[31],
+      }
+      filePlayer.gender = filePlayer.category ? 
+        filePlayer.category[0] === 'M' ? 'm' : 
+        filePlayer.category[0] === 'W' ? 
+          'w' : 
+          null : 
+        null,
+      filePlayer.doubles = !!filePlayer.category.match(/DM.*|[MW]D.*/)
+      filePlayer.junior = filePlayer.birthDate ? 
+        filePlayer.birthDate.getTime() >= (new Date((new Date()).getFullYear() - 19, 11, 31, 23, 59, 59, 999)).getTime() : 
+        false
+      filePlayer.fullName = filePlayer.doubles ? 
+        `${filePlayer.name} ${filePlayer.firstName} / ${filePlayer.nameDP} ${filePlayer.firstNameDP}` : 
+        `${filePlayer.name} ${filePlayer.firstName}`
+      filePlayer.idString = `${filePlayer.category} % ${filePlayer.fullName}`
+      filePlayer.phone = null
+      const reMobile = /^\+417/
+      if (filePlayer.phoneWork && filePlayer.phoneWork.match(reMobile)) {
+        filePlayer.phone = filePlayer.phoneWork
+      } else if (filePlayer.phonePrivate && filePlayer.phonePrivate.match(reMobile)) {
+        filePlayer.phone = filePlayer.phonePrivate 
+      } else if (filePlayer.phoneMobile && filePlayer.phoneMobile.match(reMobile)) {
+        filePlayer.phone = filePlayer.phoneMobile
+      }
+
+      const dbPlayer = dbPlayers.filter(player => player.idString == filePlayer.idString).sort((a, b) => 
+        a.created > b.created ? 1 : a.created == b.created ? 0 : -1
+      )[0]
+      console.log('sorted:', dbPlayer)
+
+      if (filePlayer != dbPlayer) {
+        const player = new Player(filePlayer)
+        player.save()
+        return player._id
+      } else {
+        return dbPlayer._id
+      }
     })
+
+    const playerList = new PlayerList({
+      imported: new Date(),
+      file: req.params.filename,
+      fileSize: stat.size,
+      players: allPlayers
+    })
+    playerList.save()
+      
+    return res.json({ playerList })
+  } catch (error) {
+    return res.status(400).json({ msg: error.toString() })
   }
 })
 
-swisstennis.get('/download/calendar/:tournament', async (req, res) => {
+// List downloaded files
+swisstennis.get('/files/list/:tournament', async (req, res) => {
   const { tournament } = req.params
-
+  
   if (!tournament) {
-    res.json({
-      success: false,
-      msg: 'No tournament given.'
+    return res.json({ msg: 'No tournament given.' })
+  }
+  try {
+    const dirContent = await awaitFs.readdir('swisstennis_files')
+    const fileList = dirContent.filter(filename => {
+      console.log(filename)
+      return filename.includes(tournament) || true
+    }).map(filename => {
+      console.log(filename)
+      const stats = fs.statSync(`swisstennis_files/${filename}`)
+      return { [filename]: stats.size }
     })
-    return
+    return res.json({ fileList })
+  } catch (error) {
+    return res.status(400).json({ msg: error.toString() })
+  }
+})
+
+swisstennis.get('/calendar/download/:tournament', async (req, res) => {
+  const { tournament } = req.params
+  
+  if (!tournament) {
+    return res.json({ msg: 'No tournament given.' })
   }
   try {
     const calendarFile = fs.createWriteStream(`swisstennis_files/Calendar-${fileDate(new Date())}.xls`)
     const calendar = await session.get(`https://comp.swisstennis.ch/advantage/servlet/Calendar.xls?Lang=D&tournament=Id${tournament}&Type=Match&Inp_DateRangeFilter.fromDate=04.06.2018&Inp_DateRangeFilter.toDate=16.09.2018`, {stream: true})
     calendar.pipe(calendarFile)
-    res.json({
-      success: true,
-      msg: 'Download successful.'
-    })
+    return res.json({ msg: 'Download successful.' })
   } catch (error) {
-    res.status(400).json({
-      success: false,
-      msg: error.toString()
-    })
+    return res.status(400).json({ msg: error.toString() })
+  }
+})
+
+swisstennis.get('/calendar/parse/:filename', async (req,res) => {
+  try {
+    console.log('Parsing file', req.params.filename)
+    console.log('About to read the calendar list.')
+    
+    const worksheets = await Excel.readWorkbook(`swisstennis_files/${req.params.filename}`)
+    const worksheet = worksheets.Sheet1
+    if (worksheet[2].length < 8 | worksheet[2].length > 9) {
+      throw Error(`Wrong file structure.`)
+    }
+    const calendar = await Promise.all(worksheet.slice(2, worksheet.length).map(async data => {
+      const category = normalize(data[3])
+      const player1 = await Player.findOne({idString: `${category} % ${normalize(data[4])}`})
+      const player2 = await Player.findOne({idString: `${category} % ${normalize(data[6])}`})
+      const match = new Match({
+        _created: Date.now(),
+        idString: "String",
+        place: normalize(data[0]),
+        date: data[1],
+        category,
+        player1: player1 ? player1._id : null,
+        player2: player2 ? player2._id : null,
+        result: normalize(data[8] || null),
+        doubles: !!category.match(/DM.*|[MW]D.*/)
+      })
+
+      match.save()
+      return match
+    }))
+    res.json(calendar)
+  } catch (error) {
+    res.status(400).json({ msg: error.toString() })
   }
 })
 
 swisstennis.get('/download/draw/:draw', async (req, res) => {
   const { draw } = req.params
-
+  
   if (!draw) {
-    res.json({
-      success: false,
-      msg: 'No draw given.'
-    })
-    return
+    return res.json({ msg: 'No draw given.' })
   }
   try {
     const fileName = `DisplayDraw${draw}-${fileDate(new Date())}.xls`
     const drawFile = fs.createWriteStream(`swisstennis_files/${fileName}`)
     const drawDisplay = await session.get(`https://comp.swisstennis.ch/advantage/servlet/DisplayDraw.xls?eventId=${draw}&lang=D`, {stream: true})
     drawDisplay.pipe(drawFile)
-    res.json({
-      success: true,
-      fileName
-    })
+    return res.json({ fileName })
   } catch (error) {
-    res.status(400).json({
-      success: false,
-      msg: error.toString()
-    })
+    return res.status(400).json({ msg: error.toString() })
   }
 })
 
-function generatePlayerList (file) {
-  return new Promise((resolve, reject) => {
-    console.log('About to read the player list.')
-    Excel.readWorkbook(file).then(worksheets => {
-      console.log('got worksheets', worksheets)
-      const worksheet = worksheets.Players
-      if (worksheet[4].length !== 32 & worksheet[3][0] !== 'Konkurrenz' & worksheet[3][31] !== 'bezahlt') {
-        reject(Error(`Wrong file structure. Length: ${worksheet[4].length} (expected 32), Column A name: ${worksheet[3][0]} (expected Konkurrenz)`))
-      }
-      const headers = worksheet.slice(3, 1)
-      const allPlayers = worksheet.slice(4, worksheet.length).map(playerData => new Player.Player(playerData))
-      resolve({ headers, allPlayers })
-    }).catch(error => {
-      reject(Error(`Error reading workbook ${error.toString()}`))
-    })
-  })
-}
+swisstennis.get('/schedule/generate/:id', async (req, res) => {
+  return res.status(400).json({ msg: 'Not implemented' })
+})
+
+swisstennis.get('/paylist/generate/:id', async (req, res) => {
+  return res.status(400).json({ msg: 'Not implemented' })
+})
 
 export default swisstennis

+ 37 - 0
server/src/restServer/routes/users.js

@@ -1,5 +1,9 @@
 import express from 'express'
+import pdfMake from 'pdfmake/build/pdfmake'
+import pdfFonts from 'pdfmake/build/vfs_fonts'
+import fs from 'fs'
 import User from '../models/user'
+pdfMake.vfs = pdfFonts.pdfMake.vfs
 
 const users = express.Router()
 
@@ -19,6 +23,39 @@ users.get('/', (req, res) => {
   })
 })
 
+users.get('/pdf', async (req, res) => {
+    try {
+        const dbUsers = await User.find().lean().exec()
+        const docDefinition = {
+            content: [
+                { text: 'Benutzerliste', style: 'header'},
+                { table: {
+                    headerRows: 1,
+                    widths: ['20%', '*', '30%'],
+                    body: Array.concat(
+                        [['Benutzername', 'Name', 'Passwort-Hash']],
+                        dbUsers.map((user) => {
+                            return [user.username, user.name, user.password]
+                        }))
+                } }
+            ],
+            styles: {
+                header: {
+                    fontSize: 22,
+                    bold: true
+                }
+            }
+        }
+        const pdfDocGenerator = pdfMake.createPdf(docDefinition)
+        pdfDocGenerator.getBuffer((buffer) => {
+            fs.writeFileSync('/usr/src/sztm_files/users.pdf', new Buffer(new Uint8Array(buffer)))
+        })
+        res.send("Alles paletti")
+    } catch (error) {
+        return res.json({ msg: error.toString() })
+    }
+})
+
 users.get('/:username', (req, res) => {
     const { username } = req.params
 

Деякі файли не було показано, через те що забагато файлів було змінено