diff --git a/app.js b/app.ts similarity index 77% rename from app.js rename to app.ts index c88c10d..8ab053e 100644 --- a/app.js +++ b/app.ts @@ -1,7 +1,8 @@ -const express = require('express') -const helmet = require('helmet') -const cors = require('cors') -const morgan = require('morgan') +import express from 'express' +import helmet from 'helmet' +import cors from 'cors' +import morgan from 'morgan' +import api from './src/api' const app = express() @@ -44,8 +45,8 @@ app.use( app.use(express.json()) -const api = require('./src/api') - app.use(api) +export default app +// support CommonJS default import module.exports = app diff --git a/index.js b/index.ts similarity index 88% rename from index.js rename to index.ts index 2eba0c2..40a83fd 100644 --- a/index.js +++ b/index.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -const http = require('http') -const app = require('./app') +import http from 'http' +import app from './app' /** * Create HTTP server. @@ -10,7 +10,7 @@ const server = http.createServer(app) /** * Normalize a port into a number, string, or false. */ -const normalizePort = (val) => { +const normalizePort = (val: string): number | string | false => { const numericPort = parseInt(val, 10) // eslint-disable-next-line no-restricted-globals @@ -36,7 +36,7 @@ app.set('port', port) /** * Event listener for HTTP server "error" event. */ -const onError = (error) => { +const onError = (error: NodeJS.ErrnoException) => { if (error.syscall !== 'listen') { throw error } @@ -63,7 +63,7 @@ const onError = (error) => { */ const onListening = () => { const addr = server.address() - const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}` + const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${(addr as any).port}` console.log(`Listening on ${bind}`) } diff --git a/package.json b/package.json index cb503eb..6141c0b 100644 --- a/package.json +++ b/package.json @@ -2,21 +2,24 @@ "name": "astrology_api", "version": "1.3.0", "description": "Api para el calculos astrologicos", - "main": "index.js", + "main": "dist/index.js", "engines": { "node": ">=14.x" }, "scripts": { - "start": "node index.js", - "dev": "nodemon --watch src src/index.js", + "build": "tsc", + "start": "node dist/index.js", + "dev": "ts-node index.ts", "test": "ENVIRONMENT=test jest", "coverage": "ENVIRONMENT=test jest --coverage", "coveralls": "ENVIRONMENT=test jest --coverage && cat ./coverage/lcov.info | coveralls", "lint": "./node_modules/.bin/eslint --fix src" }, "jest": { + "preset": "ts-jest", + "testEnvironment": "node", "coveragePathIgnorePatterns": [ - "./app.js" + "./app.ts" ] }, "keywords": [ @@ -26,6 +29,7 @@ "devDependencies": { "@types/express": "^4.17.6", "@types/jest": "^25.2.1", + "@types/node": "^18.0.0", "coveralls": "^3.0.9", "eslint": "^8.15.0", "eslint-config-standard": "^17.0.0", @@ -34,7 +38,10 @@ "eslint-plugin-promise": "^6.0.0", "jest": "^28.1.0", "nodemon": "^2.0.4", - "supertest": "^4.0.2" + "supertest": "^4.0.2", + "ts-jest": "^28.0.0", + "ts-node": "^10.9.1", + "typescript": "^5.0.0" }, "dependencies": { "cors": "^2.8.5", diff --git a/src/api/index.js b/src/api/index.js deleted file mode 100644 index 6cf8ee7..0000000 --- a/src/api/index.js +++ /dev/null @@ -1,19 +0,0 @@ -const Router = require('express-promise-router') -const astrologer = require('../astrologer') - -const router = new Router() - -router.get('/', async (req, res) => res.status(200).json({ message: 'Welcome to Astrology api!' })) - -router.get('/horoscope', async (req, res) => { - const date = new Date(req.query.time) - const { latitude, longitude, houseSystem } = req.query - - const chart = astrologer.natalChart(date, latitude, longitude, houseSystem) - - res.status(200).json({ - data: chart - }) -}) - -module.exports = router diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..e937fef --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,19 @@ +import Router from 'express-promise-router' +import astrologer from '../astrologer' + +const router = new Router() + +router.get('/', async (_req, res) => res.status(200).json({ message: 'Welcome to Astrology api!' })) + +router.get('/horoscope', async (req, res) => { + const date = new Date(req.query.time as string) + const { latitude, longitude, houseSystem } = req.query as Record + + const chart = astrologer.natalChart(date, latitude, longitude, houseSystem) + + res.status(200).json({ + data: chart + }) +}) + +export default router diff --git a/src/astrologer/aspects.js b/src/astrologer/aspects.js deleted file mode 100644 index 57a3682..0000000 --- a/src/astrologer/aspects.js +++ /dev/null @@ -1,126 +0,0 @@ -const { normalizeDegrees } = require('./utils') - -const ASPECTS = { - 0: 'conjunction', - 30: 'semisextile', - 60: 'sextile', - 90: 'quadrature', - 120: 'trigone', - 150: 'quincunx', - 180: 'opposition' -} - -// HUBER ORBS... but mars and jupiter modified... -const DEFAULT_ORBS = { - luminary: { - 0: 10, - 30: 3, - 60: 5, - 90: 6, - 120: 8, - 150: 5, - 180: 10 - }, - personal: { - 0: 7, - 30: 2, - 60: 4, - 90: 5, - 120: 6, - 150: 2, - 180: 7 - }, - social: { - 0: 6, - 30: 1.5, - 60: 3, - 90: 4, - 120: 5, - 150: 3, - 180: 6 - }, - transpersonal: { - 0: 5, - 30: 1, - 60: 2, - 90: 3, - 120: 4, - 150: 2, - 180: 5 - }, - other: { - 0: 5, - 30: 1, - 60: 2, - 90: 3, - 120: 4, - 150: 2, - 180: 5 - } -} - -const calculateAspect = (first, second, orbs) => { - return Object.keys({ ...ASPECTS }).filter( - (a) => { - const totalOrbsForAspect = orbs[a] - const from = parseFloat(a) - (totalOrbsForAspect / 2) - const to = parseFloat(a) + (totalOrbsForAspect / 2) - - const firstLongitude = normalizeDegrees(first.position.longitude) - const secondLongitude = normalizeDegrees(second.position.longitude) - - const diff = Math.abs(firstLongitude - secondLongitude) - return diff >= from && diff <= to - } - ) -} - -const aspect = (first, second, orbs) => { - if (orbs === undefined) { - orbs = { ...DEFAULT_ORBS } - } - - const aspectsFirst = calculateAspect(first, second, orbs[first.type]) - const aspectsSecond = calculateAspect(first, second, orbs[second.type]) - - if (aspectsFirst.length === 0 && aspectsSecond.length === 0) { - return undefined - } - - const direction = aspectsFirst.length === 1 && aspectsSecond.length === 1 ? 'bidirectional' : 'unidirectional' - - return { - name: ASPECTS[aspectsFirst[0]], - direction, - first: { - name: first.name, - exist: aspectsFirst.length === 1 - }, - second: { - name: second.name, - exist: aspectsSecond.length === 1 - } - } -} - -const aspects = (planets) => { - return Object.keys(planets).reduce((acc, planetKey) => { - acc[planetKey] = [] - - Object.values(planets).filter((p) => p.name !== planetKey).forEach((p) => { - if (!acc[p.name]) { - const aspectsFounds = aspect(planets[planetKey], p) - if (aspectsFounds) { - acc[planetKey].push(aspectsFounds) - } - } - }) - - return acc - }, {}) -} - -module.exports = { - aspect, - aspects -} diff --git a/src/astrologer/aspects.ts b/src/astrologer/aspects.ts new file mode 100644 index 0000000..1e468b7 --- /dev/null +++ b/src/astrologer/aspects.ts @@ -0,0 +1,167 @@ +import { normalizeDegrees } from './utils' + +// Supported angular distances between planets and their corresponding names +const ASPECTS: Record = { + 0: 'conjunction', + 30: 'semisextile', + 60: 'sextile', + 90: 'quadrature', + 120: 'trigone', + 150: 'quincunx', + 180: 'opposition' +} + +// Classification of planets used by the aspect calculator +export type PlanetType = + | 'luminary' + | 'personal' + | 'social' + | 'transpersonal' + | 'other' + +export interface Position { + longitude: number +} + +export interface Planet { + name: string + type: PlanetType + position: Position +} + +export type AspectAngle = 0 | 30 | 60 | 90 | 120 | 150 | 180 + +// Default orb values for each planet type and aspect angle +export type Orbs = Record> + +const DEFAULT_ORBS: Orbs = { + luminary: { + 0: 10, + 30: 3, + 60: 5, + 90: 6, + 120: 8, + 150: 5, + 180: 10 + }, + personal: { + 0: 7, + 30: 2, + 60: 4, + 90: 5, + 120: 6, + 150: 2, + 180: 7 + }, + social: { + 0: 6, + 30: 1.5, + 60: 3, + 90: 4, + 120: 5, + 150: 3, + 180: 6 + }, + transpersonal: { + 0: 5, + 30: 1, + 60: 2, + 90: 3, + 120: 4, + 150: 2, + 180: 5 + }, + other: { + 0: 5, + 30: 1, + 60: 2, + 90: 3, + 120: 4, + 150: 2, + 180: 5 + } +} + +const calculateAspect = ( + first: Planet, + second: Planet, + orbs: Record +): AspectAngle[] => { + const firstLongitude = normalizeDegrees(first.position.longitude) + const secondLongitude = normalizeDegrees(second.position.longitude) + const diff = Math.abs(firstLongitude - secondLongitude) + + return (Object.keys(ASPECTS) as unknown as AspectAngle[]).filter((angle) => { + const totalOrbsForAspect = orbs[angle] + const from = angle - totalOrbsForAspect / 2 + const to = angle + totalOrbsForAspect / 2 + return diff >= from && diff <= to + }) +} + +export type Direction = 'bidirectional' | 'unidirectional' + +export interface AspectResult { + name: string + direction: Direction + first: { name: string; exist: boolean } + second: { name: string; exist: boolean } +} + +const aspect = ( + first: Planet, + second: Planet, + orbs: Orbs = DEFAULT_ORBS +): AspectResult | undefined => { + const aspectsFirst = calculateAspect(first, second, orbs[first.type]) + const aspectsSecond = calculateAspect(first, second, orbs[second.type]) + + if (aspectsFirst.length === 0 && aspectsSecond.length === 0) { + return undefined + } + + const angle = aspectsFirst[0] ?? aspectsSecond[0] + const direction: Direction = + aspectsFirst.length === 1 && aspectsSecond.length === 1 + ? 'bidirectional' + : 'unidirectional' + + return { + name: ASPECTS[angle], + direction, + first: { + name: first.name, + exist: aspectsFirst.length === 1 + }, + second: { + name: second.name, + exist: aspectsSecond.length === 1 + } + } +} + +export interface AspectsMap { + [planet: string]: AspectResult[] +} + +const aspects = (planets: Record, orbs: Orbs = DEFAULT_ORBS): AspectsMap => { + return Object.keys(planets).reduce((acc, planetKey) => { + acc[planetKey] = [] + + Object.values(planets) + .filter((p) => p.name !== planetKey) + .forEach((p) => { + if (!acc[p.name]) { + const aspectsFound = aspect(planets[planetKey], p, orbs) + if (aspectsFound) { + acc[planetKey].push(aspectsFound) + } + } + }) + + return acc + }, {}) +} + +export { aspect, aspects } + diff --git a/src/astrologer/astros.js b/src/astrologer/astros.ts similarity index 66% rename from src/astrologer/astros.js rename to src/astrologer/astros.ts index 69a3106..6421127 100644 --- a/src/astrologer/astros.js +++ b/src/astrologer/astros.ts @@ -1,6 +1,6 @@ -const sweph = require('sweph') -const { utcToJulianEt, zodiacSign, degreesToDms } = require('./utils') -const path = require('path') +import sweph from 'sweph' +import { utcToJulianEt, zodiacSign, degreesToDms } from './utils' +import path from 'path' sweph.set_ephe_path(path.join(__dirname, '/../../eph')) @@ -65,13 +65,13 @@ const planetsByType = { const FLAG = SEFLG_SPEED | SEFLG_SWIEPH -const getPositionOfAstro = (astro, julianDay) => sweph.calc(julianDay, PLANETS[astro], FLAG) +const getPositionOfAstro = (astro: string, julianDay: number) => sweph.calc(julianDay, PLANETS[astro], FLAG) -const isRetrograde = (speed) => speed < 0 +const isRetrograde = (speed: number) => speed < 0 -const position = (astrologyObject, moment) => { +const position = (astrologyObject: string, moment: Date) => { const julianDay = utcToJulianEt(moment) - const { data } = getPositionOfAstro(astrologyObject, julianDay) + const { data } = getPositionOfAstro(astrologyObject, julianDay) as any const longitude = data[0] const speed = data[3] const dms = degreesToDms(longitude) @@ -88,25 +88,16 @@ const position = (astrologyObject, moment) => { } } -const planets = (date) => { - return Object.keys(PLANETS) - .reduce( - (accumulator, name) => { - const planetPosition = position(name, date) - accumulator[name] = { - name, - ...planetPosition, - type: planetsByType[name] - } - return accumulator - }, - {} - ) +const planets = (date: Date) => { + return Object.keys(PLANETS).reduce((accumulator: any, name) => { + const planetPosition = position(name, date) + accumulator[name] = { + name, + ...planetPosition, + type: (planetsByType as any)[name] + } + return accumulator + }, {}) } -module.exports = { - PLANETS, - position, - planetsByType, - planets -} +export { PLANETS, position, planetsByType, planets } diff --git a/src/astrologer/charts.js b/src/astrologer/charts.ts similarity index 60% rename from src/astrologer/charts.js rename to src/astrologer/charts.ts index a442fef..52ffc2f 100644 --- a/src/astrologer/charts.js +++ b/src/astrologer/charts.ts @@ -1,8 +1,13 @@ -const { houses } = require('./houses') -const { aspects } = require('./aspects') -const { planets } = require('./astros') +import { houses } from './houses' +import { aspects } from './aspects' +import { planets } from './astros' -const natalChart = (date, latitude, longitude, houseSystem = 'P') => { +const natalChart = ( + date: Date, + latitude: string, + longitude: string, + houseSystem = 'P' +) => { const astrosList = planets(date) const aspectsList = aspects(astrosList) const housesList = houses( @@ -23,6 +28,4 @@ const natalChart = (date, latitude, longitude, houseSystem = 'P') => { } } -module.exports = { - natalChart -} +export { natalChart } diff --git a/src/astrologer/houses.js b/src/astrologer/houses.ts similarity index 56% rename from src/astrologer/houses.js rename to src/astrologer/houses.ts index dbb37be..fb443d4 100644 --- a/src/astrologer/houses.js +++ b/src/astrologer/houses.ts @@ -1,11 +1,10 @@ -const sweph = require('sweph') -const path = require('path') +import sweph from 'sweph' +import path from 'path' +import { utcToJulianUt, degreesToDms, zodiacSign } from './utils' sweph.set_ephe_path(path.join(__dirname, '/../../eph')) -const { utcToJulianUt, degreesToDms, zodiacSign } = require('./utils') - -const houses = (date, position, houseSystem = 'P') => { +const houses = (date: Date, position: any, houseSystem = 'P') => { const julianDayUT = utcToJulianUt(date) const withoutGeoposition = !(position?.latitude && position?.longitude) @@ -27,12 +26,15 @@ const houses = (date, position, houseSystem = 'P') => { position.latitude, position.longitude, houseSystem // placidus system... - ).data + ).data as any - const houseCollection = housesPositions.map((cuspid) => ({ position: degreesToDms(cuspid), sign: zodiacSign(cuspid) })) + const houseCollection = housesPositions.map((cuspid: number) => ({ position: degreesToDms(cuspid), sign: zodiacSign(cuspid) })) const axes = { - asc: houseCollection[0], dc: houseCollection[6], mc: houseCollection[9], ic: houseCollection[3] + asc: houseCollection[0], + dc: houseCollection[6], + mc: houseCollection[9], + ic: houseCollection[3] } return { @@ -41,6 +43,4 @@ const houses = (date, position, houseSystem = 'P') => { } } -module.exports = { - houses -} +export { houses } diff --git a/src/astrologer/index.js b/src/astrologer/index.js deleted file mode 100644 index 76f6037..0000000 --- a/src/astrologer/index.js +++ /dev/null @@ -1,14 +0,0 @@ -const { PLANETS, position, planets } = require('./astros') -const { houses } = require('./houses') -const { aspect, aspects } = require('./aspects') -const charts = require('./charts') - -module.exports = { - houses, - position, - PLANETS, - planets, - aspect, - aspects, - ...charts -} diff --git a/src/astrologer/index.ts b/src/astrologer/index.ts new file mode 100644 index 0000000..fa64225 --- /dev/null +++ b/src/astrologer/index.ts @@ -0,0 +1,14 @@ +import { PLANETS, position, planets } from './astros' +import { houses } from './houses' +import { aspect, aspects } from './aspects' +import * as charts from './charts' + +export { + houses, + position, + PLANETS, + planets, + aspect, + aspects, + ...charts +} diff --git a/src/astrologer/utils.js b/src/astrologer/utils.ts similarity index 61% rename from src/astrologer/utils.js rename to src/astrologer/utils.ts index 4fef73b..fa6a553 100644 --- a/src/astrologer/utils.js +++ b/src/astrologer/utils.ts @@ -1,9 +1,9 @@ -const sweph = require('sweph') -const path = require('path') +import sweph from 'sweph' +import path from 'path' sweph.set_ephe_path(path.join(__dirname, '/../../eph')) -const utcToJulianUt = (utcDate) => { +const utcToJulianUt = (utcDate: Date) => { const milliSecondsInSeconds = utcDate.getUTCMilliseconds() / 1000 const secondsInMinutes = (utcDate.getUTCSeconds() + milliSecondsInSeconds) / 60 const minutesInHours = (utcDate.getUTCMinutes() + secondsInMinutes) / 60 @@ -19,14 +19,14 @@ const utcToJulianUt = (utcDate) => { ) } -const utcToJulianEt = (utcDate) => { +const utcToJulianEt = (utcDate: Date) => { const julianUt = utcToJulianUt(utcDate) - const delta = sweph.deltat(julianUt) + const delta = (sweph as any).deltat(julianUt) return julianUt + delta } -const degreesToDms = (value) => { - const position = sweph.split_deg(value, sweph.constants.SE_SPLIT_DEG_ZODIACAL) +const degreesToDms = (value: number) => { + const position = sweph.split_deg(value, (sweph as any).constants.SE_SPLIT_DEG_ZODIACAL) const { degree: degrees, minute: minutes, second: seconds } = position return { degrees, @@ -36,9 +36,9 @@ const degreesToDms = (value) => { } } -const zodiacSign = (degrees) => (Math.floor(degrees / 30) % 12) + 1 +const zodiacSign = (degrees: number) => (Math.floor(degrees / 30) % 12) + 1 -const normalizeDegrees = (degrees) => { +const normalizeDegrees = (degrees: number) => { if (degrees < -180) { return degrees + 360 } @@ -49,10 +49,4 @@ const normalizeDegrees = (degrees) => { return degrees } -module.exports = { - utcToJulianUt, - degreesToDms, - zodiacSign, - normalizeDegrees, - utcToJulianEt -} +export { utcToJulianUt, degreesToDms, zodiacSign, normalizeDegrees, utcToJulianEt } diff --git a/test/features/empty-houses-and-axes-when-not-send-geoposition.test.js b/test/features/empty-houses-and-axes-when-not-send-geoposition.test.js index f022af8..eb2eacf 100644 --- a/test/features/empty-houses-and-axes-when-not-send-geoposition.test.js +++ b/test/features/empty-houses-and-axes-when-not-send-geoposition.test.js @@ -1,5 +1,5 @@ const request = require('supertest') -const app = require('./../../app') +const app = require('./../../app').default describe('Get houses empty and axes as undefined when geolocation is not send', () => { let response diff --git a/test/features/get-houses-cuspids-in-placidus-system.spec.js b/test/features/get-houses-cuspids-in-placidus-system.spec.js index e033a6f..3249e73 100644 --- a/test/features/get-houses-cuspids-in-placidus-system.spec.js +++ b/test/features/get-houses-cuspids-in-placidus-system.spec.js @@ -1,5 +1,5 @@ const request = require('supertest') -const app = require('./../../app') +const app = require('./../../app').default describe('Get placidus houses system cuspids for 1991-07-06T16:50:00-04:00', () => { let response diff --git a/test/features/get-planets-positions-for-06-07-1991-205000gmt.spec.js b/test/features/get-planets-positions-for-06-07-1991-205000gmt.spec.js index 3d49eea..075dad1 100644 --- a/test/features/get-planets-positions-for-06-07-1991-205000gmt.spec.js +++ b/test/features/get-planets-positions-for-06-07-1991-205000gmt.spec.js @@ -1,5 +1,5 @@ const request = require('supertest') -const app = require('./../../app') +const app = require('./../../app').default describe('Get the planets position for 1991-07-06T16:50:00-04:00', () => { let response diff --git a/test/features/support-additionals-house-systems.spec.js b/test/features/support-additionals-house-systems.spec.js index 1e970d1..71e64b1 100644 --- a/test/features/support-additionals-house-systems.spec.js +++ b/test/features/support-additionals-house-systems.spec.js @@ -1,5 +1,5 @@ const request = require('supertest') -const app = require('./../../app') +const app = require('./../../app').default describe('Get Koch houses system cuspids for 1991-07-06T16:50:00-04:00', () => { let response diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..973efec --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "esModuleInterop": true, + "strict": true, + "outDir": "dist", + "rootDir": ".", + "moduleResolution": "node", + "resolveJsonModule": true, + "skipLibCheck": true + }, + "include": ["src/**/*", "index.ts", "app.ts", "types/**/*.d.ts"], + "exclude": ["node_modules", "dist", "test"] +} diff --git a/types/sweph.d.ts b/types/sweph.d.ts new file mode 100644 index 0000000..31b1984 --- /dev/null +++ b/types/sweph.d.ts @@ -0,0 +1 @@ +declare module 'sweph';