Skip to content
Open
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
3 changes: 3 additions & 0 deletions cron/crontab
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Cron job to run full backup script every hour
0 * * * * /usr/local/bin/node /app/scripts/fullBackup.js >> /var/log/cron.log 2>&1

# Cron job to run anonymize script every month on the 21st at 3:00 AM
* * * * * /usr/local/bin/node /app/scripts/anonymize.js >> /var/log/cron.log 2>&1
38 changes: 38 additions & 0 deletions cron/scripts/anonymize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pg from 'pg';
const { Client } = pg;
import 'dotenv/config';

function getTimestamp(monthOffset) {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + monthOffset;
const date = new Date(year, month, 1, 0, 0, 0);
return Math.floor(date.getTime());
}

async function main() {
const t1 = getTimestamp(-1);
const t2 = getTimestamp(0) - 24 * 60 * 60 * 1000;

const client = new Client({
host: 'pg',
user: 'postgres',
password: 'pw',
database: 'prima'
});

try {
await client.connect();
await client.query(`CALL anonymize($1, $2)`, [t1, t2]);
console.log(
`Anonymization successful between ${new Date(t1).toISOString()} and ${new Date(t2).toISOString()}`
);
} catch (error) {
console.error('Failed to run anonymization procedure:', error);
process.exit(1);
} finally {
await client.end();
}
}

main();
89 changes: 89 additions & 0 deletions migrations/2025-04-20-anonymize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { sql } from 'kysely';

export async function up(db) {
await sql`CREATE OR REPLACE PROCEDURE anonymize(t1 DOUBLE PRECISION, t2 DOUBLE PRECISION)
AS $$
DECLARE
j RECORD;
legs JSONB;
leg JSONB;
leg_index INT;
BEGIN
-- Anonymize request table
UPDATE request
SET customer = null
FROM tour
WHERE tour.id = request.tour
AND tour.arrival >= t1
AND tour.arrival < t2;

-- Anonymize event table
UPDATE event
SET lat = ROUND(event.lat::numeric, 2),
lng = ROUND(event.lng::numeric, 2),
address = 'anonymer Ort'
FROM request
INNER JOIN tour ON request.tour = tour.id
WHERE event.request = request.id
AND tour.arrival > t1
AND tour.arrival < t2;

-- Anonymize journey table
FOR j IN
SELECT journey.id, journey.json
FROM journey
LEFT JOIN request r1 ON journey.request1 = r1.id
LEFT JOIN request r2 ON journey.request2 = r2.id
LEFT JOIN tour tour1 ON r1.tour = tour1.id
LEFT JOIN tour tour2 ON r2.tour = tour2.id
WHERE
journey.json->'legs'->0->'from'->>'departure' ~ '^\d+(\.\d+)?$' AND
(journey.json->'legs'->0->'from'->>'departure')::double precision > t1 AND
(journey.json->'legs'->0->'from'->>'departure')::double precision < t2
LOOP
UPDATE journey SET "user" = NULL WHERE id = j.id;
legs := j.json->'legs';
leg_index := 0;
FOR leg IN
SELECT * FROM jsonb_array_elements(legs) AS leg
LOOP
IF leg->>'mode' = 'ODM' THEN
j.json := jsonb_set(
j.json,
ARRAY['legs', leg_index::text, 'from', 'lat'],
ROUND((leg->'from'->>'lat')::numeric, 2)::text::jsonb
);
j.json := jsonb_set(
j.json,
ARRAY['legs', leg_index::text, 'from', 'lon'],
ROUND((leg->'from'->>'lon')::numeric, 2)::text::jsonb
);
j.json := jsonb_set(
j.json,
ARRAY['legs', leg_index::text, 'from', 'name'],
'"anonymer Ort"'::jsonb
);
j.json := jsonb_set(
j.json,
ARRAY['legs', leg_index::text, 'to', 'lat'],
ROUND((leg->'to'->>'lat')::numeric, 2)::text::jsonb
);
j.json := jsonb_set(
j.json,
ARRAY['legs', leg_index::text, 'to', 'lon'],
ROUND((leg->'to'->>'lon')::numeric, 2)::text::jsonb
);
j.json := jsonb_set(
j.json,
ARRAY['legs', leg_index::text, 'to', 'name'],
'"anonymer Ort"'::jsonb
);
END IF;
leg_index := leg_index + 1;
END LOOP;
UPDATE journey SET json = j.json WHERE id = j.id;
END LOOP;
END;
$$ LANGUAGE plpgsql;
`.execute(db);
}
11 changes: 11 additions & 0 deletions migrations/2025-05-03-nullable-customer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export async function up(db) {
await db.schema.alterTable('request')
.alterColumn('customer', (col) => col.dropNotNull())
.execute()

await db.schema.alterTable('journey')
.alterColumn('user', (col) => col.dropNotNull())
.execute()
}

export async function down() { }
1 change: 1 addition & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export const MONTHS = [
'Dezember'
];
export const QUARTERS = ['Quartal 1', 'Quartal 2', 'Quartal 3', 'Quartal 4'];
export const anonymouseCustomerName = 'anonymisiert';
11 changes: 11 additions & 0 deletions src/lib/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ const translations: Translations = {
'Bitte prüfen Sie Ihr E-Mail-Postfach um Ihre neue E-Mail-Adresse zu verifizieren.',
passwordResetSuccess:
'Passwort erfolgreich zurückgesetzt. Sie können sich jetzt mit dem neuen Passwort anmelden.',
remainingTours:
'Ihr Account kann nicht gelöscht werden so lange sie der einzige Unternehmensadministrator in ihrem Unternehmen sind und es noch geplante Fahrten für Ihr Unternehmen gibt.',
remainingAvailabilities:
'Ihr Account kann nicht gelöscht werden so lange sie der einzige Unternehmensadministrator in ihrem Unternehmen sind und es noch Verfügbarkeiten in der Zukunft für Ihr Unternehmen gibt.',
remainingRequests:
'Ihr Account kann nicht gelöscht werden so lange sie ausstehende Taxifahrten haben.',
remainingToursAndAvailabilities:
'Ihr Account kann nicht gelöscht werden so lange sie der einzige Unternehmensadministrator in ihrem Unternehmen sind und es noch geplante Fahrten und Verfügbarkeiten in der Zukunft für Ihr Unternehmen gibt.',
deleteAccount:
'Vorsicht, das Löschen Ihres Accounts kann nicht Rückgängig gemacht werden. Wir können Ihren Account nur löschen, falls Sie in der Zukunft keine geplanten Fahrten haben.',

// Admin
userDoesNotExist: 'Nutzer existiert nicht.',
Expand Down Expand Up @@ -130,6 +140,7 @@ const translations: Translations = {
changePhoneSubtitle: 'Ändern Sie Ihre Telefonnummer.',
logout: 'Abmelden',
logoutSubtitle: 'Aus dem Konto abmelden. Sie können sich jederzeit wieder anmelden.',
deleteAccount: 'Konto löschen',
code: 'Code',
passwordReset: 'Passwort zurücksetzen',
passwordResetSubtitle: 'Hier können Sie ein neues Passwort festlegen.',
Expand Down
10 changes: 10 additions & 0 deletions src/lib/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ const translations: Translations = {
checkInboxToVerify: 'Please check your inbox to verify your new email address.',
passwordResetSuccess:
'Your password has been reset successfully. Please login with your new password.',
remainingTours:
'We cannot delete your account while you are the only administrator of your company and there are planned tours for your company.',
remainingAvailabilities:
'We cannot delete your account while you are the only administrator of your company and there are availabilities in the future for your company.',
remainingRequests: 'We cannot delete your account while you have planned journeys.',
remainingToursAndAvailabilities:
'We cannot delete your account while you are the only administrator of your company and there are planned tours and availabilities in the future for your company.',
deleteAccount:
'Deleting your account can not be undone! We can only remove your account, if you have no future planned tours.',

// Admin
userDoesNotExist: 'User does not exist.',
Expand Down Expand Up @@ -125,6 +134,7 @@ const translations: Translations = {
changePhoneSubtitle: 'Change your phone number.',
logout: 'Logout',
logoutSubtitle: 'Log out of your account. You can log in later any time.',
deleteAccount: 'Delete Account',
code: 'Code',
passwordReset: 'Reset Password',
passwordResetSubtitle: 'Choose a new password.',
Expand Down
6 changes: 6 additions & 0 deletions src/lib/i18n/translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export type Translations = {
oldEmail: string;
checkInboxToVerify: string;
passwordResetSuccess: string;
remainingTours: string;
remainingAvailabilities: string;
remainingRequests: string;
remainingToursAndAvailabilities: string;
deleteAccount: string;

// Admin
userDoesNotExist: string;
Expand Down Expand Up @@ -124,6 +129,7 @@ export type Translations = {
changePhoneSubtitle: string;
logout: string;
logoutSubtitle: string;
deleteAccount: string;
code: string;
passwordReset: string;
passwordResetSubtitle: string;
Expand Down
5 changes: 3 additions & 2 deletions src/lib/server/db/getTours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { db } from '.';
import type { UnixtimeMs } from '$lib/util/UnixtimeMs';
import { sql } from 'kysely';
import { anonymouseCustomerName } from '$lib/constants';

export const getTours = async (
selectCancelled: boolean,
Expand Down Expand Up @@ -112,11 +113,11 @@ export const getToursWithRequests = async (
.selectFrom('event')
.innerJoin('request', 'request.id', 'event.request')
.whereRef('tour.id', '=', 'request.tour')
.innerJoin('user', 'user.id', 'request.customer')
.leftJoin('user', 'user.id', 'request.customer')
.$if(!selectCancelled, (qb) => qb.where('tour.cancelled', '=', false))
.select([
'tour.id as tour',
'user.name as customerName',
sql<string>`COALESCE("user".name, ${anonymouseCustomerName})`.as('customerName'),
'user.phone as customerPhone',
'event.id',
'event.communicatedTime',
Expand Down
4 changes: 2 additions & 2 deletions src/lib/server/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ export interface Database {
bikes: number;
luggage: number;
tour: number;
customer: number;
customer: number | null;
ticketCode: string;
ticketChecked: boolean;
cancelled: boolean;
};
journey: {
id: Generated<number>;
json: Itinerary;
user: number;
user: number | null;
request1: number | null;
request2: number | null;
rating: number | null;
Expand Down
Loading