Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9,724 changes: 4,066 additions & 5,658 deletions package-lock.json

Large diffs are not rendered by default.

81 changes: 42 additions & 39 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,51 +21,54 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^10.3.10",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.3.10",
"@nestjs/event-emitter": "^2.0.4",
"@nestjs/common": "^11.1.2",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.1.2",
"@nestjs/event-emitter": "^3.0.1",
"@nestjs/mapped-types": "*",
"@nestjs/mongoose": "^10.0.10",
"@nestjs/platform-express": "^10.3.10",
"@nestjs/schedule": "^4.1.0",
"@nestjs/swagger": "^7.4.0",
"bcrypt": "^5.1.1",
"@nestjs/mongoose": "^11.0.3",
"@nestjs/platform-express": "^11.1.2",
"@nestjs/platform-socket.io": "^11.1.2",
"@nestjs/schedule": "^6.0.0",
"@nestjs/serve-static": "^5.0.3",
"@nestjs/swagger": "^11.2.0",
"@nestjs/websockets": "^11.1.2",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"class-validator": "^0.14.2",
"handlebars": "^4.7.8",
"mongoose": "^8.5.1",
"npm-check-updates": "^16.14.20",
"reflect-metadata": "^0.1.13",
"mongoose": "^8.15.1",
"npm-check-updates": "^18.0.1",
"reflect-metadata": "^0.2.2",
"resend": "^4.5.1",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
"rimraf": "^6.0.1",
"rxjs": "^7.8.2",
"socket.io": "^4.8.1"
},
"devDependencies": {
"@golevelup/ts-jest": "^0.3.4",
"@nestjs/cli": "^10.4.2",
"@nestjs/schematics": "^10.1.3",
"@nestjs/testing": "^10.3.10",
"@types/bcrypt": "^5.0.0",
"@types/cron": "^2.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.1.3",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.8",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.0",
"typescript": "^4.7.4"
"@golevelup/ts-jest": "^0.7.0",
"@nestjs/cli": "^11.0.7",
"@nestjs/schematics": "^11.0.5",
"@nestjs/testing": "^11.1.2",
"@types/bcrypt": "^5.0.2",
"@types/express": "^5.0.2",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.23",
"@types/supertest": "^6.0.3",
"@typescript-eslint/eslint-plugin": "^8.33.0",
"@typescript-eslint/parser": "^8.33.0",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.0",
"jest": "^29.7.0",
"prettier": "^3.5.3",
"source-map-support": "^0.5.21",
"supertest": "^7.1.1",
"ts-jest": "^29.3.4",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3"
},
"jest": {
"moduleFileExtensions": [
Expand Down
223 changes: 223 additions & 0 deletions public/tripnotify/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
<!-- TODO: use local assets instead of CDN in production -->
<!DOCTYPE html>
<html lang="de">

<head>
<meta charset="UTF-8" />
<title>TripNotify</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<style>
body,
html {
margin: 0;
height: 100%;
font-family: sans-serif;
}

#app {
display: flex;
height: 100%;
}

#map {
flex: 1;
}

#sidebar {
width: 300px;
padding: 1rem;
background: #f4f4f4;
overflow-y: auto;
border-left: 1px solid #ccc;
}

.info-group {
margin-bottom: 1rem;
}

.timeline li {
margin-bottom: 1rem;
}

.timeline li:hover {
cursor: pointer;
}

.timeline li::before {
content: "⚡";
margin-right: 0.5rem;
}

#error {
border-radius: 5px;
padding: 0.75rem;
margin-top: 1rem;
font-weight: bold;
}

#error.error {
background-color: #ffe5e5;
color: #a00;
border: 1px solid #a00;
}
</style>
</head>

<body>
<div id="app">
<div id="map"></div>
<div id="sidebar">
<h2>Trip information</h2>
<div id="error" class="error"></div>
<div id="info">
<div class="info-group"><strong>Name:</strong> <span id="name">–</span></div>
<div class="info-group"><strong>Start:</strong> <span id="start">–</span></div>
<div class="info-group"><strong>End:</strong> <span id="end">–</span></div>
<div class="info-group"><strong>Consumed energy:</strong> <span id="consumedEnergy">–</span></div>
<div class="info-group"><strong>(Re-)Charged energy:</strong> <span id="chargedEnergy">–</span></div>
<div class="info-group"><strong>Ø drive speed:</strong> <span id="avgSpeed">–</span></div>
<div class="info-group"><strong>Ø charge speed:</strong> <span id="avgCharge">–</span></div>
<div class="info-group"><strong>Charge stops:</strong></div>
</div>
<ul id="chargingStops" class="timeline"></ul>
</div>
</div>

<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script>
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");

const nameEl = document.getElementById("name");
const startEl = document.getElementById("start");
const endEl = document.getElementById("end");
const consumedEl = document.getElementById("consumedEnergy");
const chargedEl = document.getElementById("chargedEnergy");
const avgSpeedEl = document.getElementById("avgSpeed");
const avgChargeEl = document.getElementById("avgCharge");
const stopsEl = document.getElementById("chargingStops");
const errorEl = document.getElementById("error");
const infoEl = document.getElementById("info");

function showError(message) {
errorEl.style.display = 'block';
errorEl.textContent = message;
}

function hideError() {
errorEl.style.display = 'none';
}

const map = L.map('map').setView([51.505, -0.09], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

let liveDataPolyline = L.polyline([], { color: 'blue' }).addTo(map);
let liveDataMarker = L.marker([0, 0]);

if (!code) {
showError("❗ No code in URL provided. Example: ?code=abc123");
} else {
fetch(`/trips/${code}`)
.then((res) => {
if (!res.ok) {
throw new Error();
}

return res.json();
})
.then((data) => {
hideError();
updateSidebarBasicInfo(data);

return fetch(`/trips/${code}/info`)
.then((res) => {
if (!res.ok) {
throw new Error();
}

return res.json();
})
})
.then((data) => {
console.log(data);
hideError();
updateSidebarMetadataAndChargingStops(data);
})
.catch((err) => {
showError("Trip could not be loaded");
});

const socket = io({
query: { code }
});

socket.on(`route-update:${code}`, (payload) => {
console.log(payload);
const lat = payload.data.latitude;
const lng = payload.data.longitude;

if (lat && lng) {
liveDataPolyline.addLatLng([lat, lng]);
liveDataMarker.setLatLng([lat, lng]);
if (!map.hasLayer(liveDataMarker)) {
liveDataMarker.addTo(map);
}
map.panTo([lat, lng]);
}
});
}

function updateSidebarBasicInfo(info) {
nameEl.textContent = info.name || "–";
startEl.textContent = info.start ? new Date(info.start).toLocaleString() : "–";
endEl.textContent = info.end ? new Date(info.end).toLocaleString() : "–";
}

function updateSidebarMetadataAndChargingStops(info) {
consumedEl.textContent = info.metadata.consumedEnergy ? info.metadata.consumedEnergy + ' kWh' : "–";
chargedEl.textContent = info.metadata.rechargedEnergy ? info.metadata.rechargedEnergy + ' kWh' : "–";
avgSpeedEl.textContent = info.metadata.avgDriveSpeed ? info.metadata.avgDriveSpeed + " km/h" : "–";
avgChargeEl.textContent = info.metadata.avgChargeSpeed ? info.metadata.avgChargeSpeed + " kW" : "–";

stopsEl.innerHTML = '';
(info.logs || []).forEach(((log) => {
if (log.type === 'charge') {
const li = document.createElement("li");

const text = `${new Date(log.startDate).toLocaleDateString()}<br> ${new Date(log.startDate).toLocaleTimeString()} ${log.endDate ? '- ' + new Date(log.endDate).toLocaleTimeString() : ' (still active)'} <br> (charged: ${(log.rechargedKWh || 0).toFixed(2)} kWh, ⌀ ${Math.abs(log.averageKW || 0).toFixed(2)} kW)`;

li.innerHTML = text;
stopsEl.appendChild(li);

log.history.some((history) => {
if (history.latitude && history.longitude) {
const marker = L.marker([history.latitude, history.longitude]).addTo(map);

marker._icon.style.filter = 'hue-rotate(260deg)';
marker.bindPopup(text);

li.onclick = () => {
map.panTo([history.latitude, history.longitude]);
};

return true;
}
});
} else {
const coordinates = log.history.map((history) => {
if (history.latitude && history.longitude) {
return [history.latitude, history.longitude];
}

return null;
}).filter((coordinates) => coordinates);

L.polyline(coordinates, { color: 'blue' }).addTo(map);
}
}));
}
</script>
</body>

</html>
2 changes: 1 addition & 1 deletion src/account/account.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('AccountGuard', () => {

await expect(async () => {
await accountGuard.canActivate(mockContext);
}).rejects.toThrow(UnauthorizedException);
}).rejects.toThrow(BadRequestException);
});

it('should not be able to proceed with invalid authorization header', async () => {
Expand Down
8 changes: 8 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { ScheduleModule } from '@nestjs/schedule';
import { MigrationModule } from './migration/migration.module';
import { PremiumModule } from './premium/premium.module';
import { NotificationsModule } from './notifications/notifications.module';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { TripNotifyModule } from './tripnotify/tripnotify.module';

@Module({
imports: [
Expand All @@ -22,6 +25,11 @@ import { NotificationsModule } from './notifications/notifications.module';
MigrationModule,
PremiumModule,
NotificationsModule,
TripNotifyModule,
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'public'),
serveRoot: '/frontend',
}),
],
controllers: [],
providers: [],
Expand Down
1 change: 1 addition & 0 deletions src/logs/logs.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ import { CronHandler } from './handler/cron';
{ name: LastSync.name, schema: LastSyncSchema },
]),
],
exports: [LogsService],
})
export class LogsModule {}
17 changes: 17 additions & 0 deletions src/logs/logs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,23 @@ export class LogsService {
return Promise.resolve(new LogDto(log));
}

async findLogsWithinDateRange(akey: string, start: Date, end: Date): Promise<Log[]> {
const query = {
akey,
startDate: { $gte: start },
$and: [
{
$or: [
{ endDate: { $lte: end } },
{ endDate: null, status: STATUS.RUNNING },
]
}
]
};

return this.logModel.find(query).select('-history').sort({startDate: 'desc'});
}

async findAll(akey: string, type?: TYPE): Promise<LogDto[]> {
const logs = this.logModel
.find({ akey })
Expand Down
4 changes: 2 additions & 2 deletions src/logs/schemas/log.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ export class Log {
})
dischargedKWh: number;

@Prop()
history: [Sync];
@Prop({ type: [Sync] })
history: Sync[];

@Prop()
thresholdReached: Date;
Expand Down
Loading