mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
feat(dawarich): fetch daily movement values from a Dawarich instance
Connect, status, places, settings, test, sync and disconnect replace their stubs. A nightly job (03:20) and the connect backfill read the timeline, visits and residency endpoints through safeFetch and store one row of daily values per local day: distances, places visited, minutes away from the home place and the sealed country with its travel-day verdict. A private instance needs its exact origin in DAWARICH_PRIVATE_ORIGINS. The connection joins the shared ledger and the connection health model, watched for errors only. The day carries a movement block, the model projection drops the travel country, the two movement channels join the correlation matrix under the environment module, travel days become trip suggestions, and the days travel in the environment backup section.
This commit is contained in:
1 parent
1e7fc08f57
commit
b838ed4403
95 files changed
+4466
-158
No files matched your search
@@ -1,23 +1,56 @@
|
||||
/**
|
||||
* `POST /api/environment/travel/suggestions/{id}/dismiss` — never suggest
|
||||
* this run of travel days again. Answers `{ dismissed: true }`; 404
|
||||
* `environment.travel_suggestion.not_found`. Part of the `environment`
|
||||
* module; own account only.
|
||||
* `environment.travel_suggestion.not_found` for an id that is not an open
|
||||
* suggestion. Part of the `environment` module; own account only.
|
||||
*/
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
import { apiError, apiSuccess } from "@/lib/api-response";
|
||||
import {
|
||||
openDawarichPrefs,
|
||||
saveDawarichPrefs,
|
||||
} from "@/lib/dawarich/connection";
|
||||
import { listTravelSuggestions } from "@/lib/dawarich/travel";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { requireModuleEnabled } from "@/lib/modules/gate";
|
||||
import { userDayKey } from "@/lib/tz/format";
|
||||
import { resolveUserTimezone } from "@/lib/tz/resolver";
|
||||
|
||||
type RouteParams = { params: Promise<{ id: string }> };
|
||||
|
||||
export const POST = apiHandler(
|
||||
async (_request: NextRequest, { params }: RouteParams) => {
|
||||
const { user } = await requireAuth();
|
||||
await params;
|
||||
const { id } = await params;
|
||||
annotate({ action: { name: "environment.travel_suggestion.dismiss" } });
|
||||
const gate = await requireModuleEnabled(user.id, "environment");
|
||||
if (!gate.enabled) return gate.response;
|
||||
return notImplemented("environment.travel_suggestion.dismiss");
|
||||
|
||||
const notFound = () =>
|
||||
apiError("No such travel suggestion", 404, {
|
||||
errorCode: "environment.travel_suggestion.not_found",
|
||||
});
|
||||
const row = await prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: { dawarichUrlEncrypted: true, dawarichPrefsEncrypted: true },
|
||||
});
|
||||
if (!row?.dawarichUrlEncrypted || id.length > 64) return notFound();
|
||||
const prefs = openDawarichPrefs(row.dawarichPrefsEncrypted);
|
||||
const today = userDayKey(new Date(), await resolveUserTimezone(user.id));
|
||||
const open = await listTravelSuggestions(
|
||||
user.id,
|
||||
today,
|
||||
new Set(prefs.dismissedSuggestionIds),
|
||||
);
|
||||
if (!open.some((s) => s.id === id)) return notFound();
|
||||
|
||||
await saveDawarichPrefs(user.id, {
|
||||
...prefs,
|
||||
dismissedSuggestionIds: [...prefs.dismissedSuggestionIds, id],
|
||||
});
|
||||
return apiSuccess({ dismissed: true as const });
|
||||
},
|
||||
);
|
||||
@@ -3,17 +3,39 @@
|
||||
* travel days no stored trip covers yet, as `{ id, startDate, endDate,
|
||||
* countryCode }`. Accepting one goes through `POST /api/environment/travel`
|
||||
* (the person picks the place); dismissing through the sibling route. Part of
|
||||
* the `environment` module; own account only.
|
||||
* the `environment` module; own account only. Without a Dawarich connection
|
||||
* the list is empty.
|
||||
*/
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { notImplemented } from "@/lib/http/not-implemented";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import { openDawarichPrefs } from "@/lib/dawarich/connection";
|
||||
import { listTravelSuggestions } from "@/lib/dawarich/travel";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { requireModuleEnabled } from "@/lib/modules/gate";
|
||||
import { userDayKey } from "@/lib/tz/format";
|
||||
import { resolveUserTimezone } from "@/lib/tz/resolver";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const GET = apiHandler(async () => {
|
||||
const { user } = await requireAuth();
|
||||
annotate({ action: { name: "environment.travel_suggestions.list" } });
|
||||
const gate = await requireModuleEnabled(user.id, "environment");
|
||||
if (!gate.enabled) return gate.response;
|
||||
return notImplemented("environment.travel_suggestions.get");
|
||||
|
||||
const row = await prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: { dawarichUrlEncrypted: true, dawarichPrefsEncrypted: true },
|
||||
});
|
||||
if (!row?.dawarichUrlEncrypted) return apiSuccess({ suggestions: [] });
|
||||
const prefs = openDawarichPrefs(row.dawarichPrefsEncrypted);
|
||||
const today = userDayKey(new Date(), await resolveUserTimezone(user.id));
|
||||
const suggestions = await listTravelSuggestions(
|
||||
user.id,
|
||||
today,
|
||||
new Set(prefs.dismissedSuggestionIds),
|
||||
);
|
||||
annotate({ meta: { suggestions: suggestions.length } });
|
||||
return apiSuccess({ suggestions });
|
||||
});
|
||||
Reference in new issue
Block a user