feat(weekly-summary-email-stub): Cron-stub für weekly-summary-email (Endpoint manuell trigger [tsc:fail]
This commit is contained in:
parent
df033a7b84
commit
43c191695b
@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"completed_features": [],
|
"completed_features": [],
|
||||||
"current_feature": "password-reset",
|
"current_feature": "weekly-summary-email-stub",
|
||||||
"started_at": "2026-05-23T06:57:51.069062",
|
"started_at": "2026-05-23T06:57:51.069062",
|
||||||
"attempted_features": [
|
"attempted_features": [
|
||||||
"saved-views",
|
"saved-views",
|
||||||
"webhook-trigger-events"
|
"webhook-trigger-events",
|
||||||
|
"password-reset"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1818,3 +1818,22 @@ src/index.ts(27,25): error TS2769: No overload matches this call.
|
|||||||
Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
|
Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
|
||||||
Argument of type 'Promise<FastifyMultipartPlugin>' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
|
Argument of type 'Promise<FastifyMultipartPlugin>' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
|
||||||
Type 'Promise<FastifyMultipartPlugin>' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTy
|
Type 'Promise<FastifyMultipartPlugin>' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTy
|
||||||
|
- `07:04:26` **INFO** Committed feature password-reset
|
||||||
|
- `07:04:27` **INFO** Pushed: rc=0
|
||||||
|
|
||||||
|
## Phase-3 Feature: weekly-summary-email-stub (2026-05-23 07:04:27)
|
||||||
|
|
||||||
|
- `07:04:27` **INFO** Description: Cron-stub für weekly-summary-email (Endpoint manuell triggerbar)
|
||||||
|
- `07:04:27` **INFO** Generating apps/api/src/routes/notifications.ts (Fastify-Plugin /api/notifications. POST /send-weekly-summary (admin-on…)
|
||||||
|
- `07:04:37` **INFO** wrote 1088 chars in 9.8s (attempt 1)
|
||||||
|
- `07:04:37` **INFO** Generating apps/api/src/services/email.ts (ERWEITERT — füge sendWeeklySummary(user) Methode. Fetched user's time-…)
|
||||||
|
- `07:04:55` **INFO** wrote 2190 chars in 18.4s (attempt 1)
|
||||||
|
- `07:04:55` **INFO** Running tsc --noEmit on api…
|
||||||
|
- `07:04:57` **WARN** tsc errors:
|
||||||
|
src/index.ts(27,25): error TS2769: No overload matches this call.
|
||||||
|
Overload 1 of 3, '(plugin: FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
|
||||||
|
Argument of type 'Promise<FastifyMultipartPlugin>' is not assignable to parameter of type 'FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
|
||||||
|
Type 'Promise<FastifyMultipartPlugin>' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProvider>, opts: { ...; }, done: (err?: Error | undefined) => void): void'.
|
||||||
|
Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
|
||||||
|
Argument of type 'Promise<FastifyMultipartPlugin>' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'.
|
||||||
|
Type 'Promise<FastifyMultipartPlugin>' provides no match for the signature '(instance: FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTy
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { db } from "../db"
|
|||||||
import { users, passwordResets } from "../db/schema"
|
import { users, passwordResets } from "../db/schema"
|
||||||
import { eq, and, gt } from "drizzle-orm"
|
import { eq, and, gt } from "drizzle-orm"
|
||||||
import { LoginRequestSchema, ForgotPasswordRequestSchema, ResetPasswordRequestSchema } from "@emberclone/shared"
|
import { LoginRequestSchema, ForgotPasswordRequestSchema, ResetPasswordRequestSchema } from "@emberclone/shared"
|
||||||
import { emailService } from "../services/emailService"
|
import { emailService } from "../services/email"
|
||||||
import { randomBytes } from "crypto"
|
import { randomBytes } from "crypto"
|
||||||
|
|
||||||
export default async function authRoutes(fastify: FastifyInstance) {
|
export default async function authRoutes(fastify: FastifyInstance) {
|
||||||
|
|||||||
38
apps/api/src/routes/notifications.ts
Normal file
38
apps/api/src/routes/notifications.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { FastifyInstance } from "fastify"
|
||||||
|
import { db } from "../db"
|
||||||
|
import { users } from "../db/schema"
|
||||||
|
import { emailService } from "../services/email"
|
||||||
|
|
||||||
|
export default async function notificationRoutes(fastify: FastifyInstance) {
|
||||||
|
fastify.addHook("preHandler", async (request, reply) => {
|
||||||
|
try {
|
||||||
|
await request.jwtVerify()
|
||||||
|
} catch (err) {
|
||||||
|
return reply.code(401).send({ message: "Unauthorized" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const isAdmin = (request: any) => {
|
||||||
|
return (request.user as { sub: string, role: string })?.role === "admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
fastify.post("/send-weekly-summary", async (request, reply) => {
|
||||||
|
if (!isAdmin(request)) {
|
||||||
|
return reply.code(403).send({ message: "Forbidden: Admin role required" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const allUsers = await db.select().from(users)
|
||||||
|
let sentCount = 0
|
||||||
|
|
||||||
|
for (const user of allUsers) {
|
||||||
|
try {
|
||||||
|
await emailService.sendWeeklySummary(user)
|
||||||
|
sentCount++
|
||||||
|
} catch (error) {
|
||||||
|
fastify.log.error(`Failed to send weekly summary to ${user.email}: ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sent: sentCount }
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,4 +1,7 @@
|
|||||||
import type { UserSelect as User } from "@emberclone/shared";
|
import type { UserSelect as User } from "@emberclone/shared";
|
||||||
|
import { db } from "../db";
|
||||||
|
import { timeEntries } from "../db/schema";
|
||||||
|
import { and, eq, gte } from "drizzle-orm";
|
||||||
|
|
||||||
class EmailService {
|
class EmailService {
|
||||||
private async send(to: string, subject: string, body: string) {
|
private async send(to: string, subject: string, body: string) {
|
||||||
@ -28,6 +31,27 @@ class EmailService {
|
|||||||
const body = `Hi ${user.name || "there"}, this is your daily reminder to check your EmberClone dashboard!`;
|
const body = `Hi ${user.name || "there"}, this is your daily reminder to check your EmberClone dashboard!`;
|
||||||
await this.send(user.email, subject, body);
|
await this.send(user.email, subject, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendWeeklySummary(user: User) {
|
||||||
|
const oneWeekAgo = new Date();
|
||||||
|
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
||||||
|
|
||||||
|
const entries = await db
|
||||||
|
.select()
|
||||||
|
.from(timeEntries)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(timeEntries.userId, user.id),
|
||||||
|
gte(timeEntries.createdAt, oneWeekAgo)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalHours = entries.reduce((acc, entry) => acc + (entry.duration || 0), 0) / 60;
|
||||||
|
const subject = "Your Weekly Summary";
|
||||||
|
const body = `Hi ${user.name || "there"},\n\nHere is your summary for the last 7 days:\n- Total entries: ${entries.length}\n- Total time tracked: ${totalHours.toFixed(2)} hours\n\nKeep up the great work!`;
|
||||||
|
|
||||||
|
await this.send(user.email, subject, body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const emailService = new EmailService();
|
export const emailService = new EmailService();
|
||||||
Loading…
Reference in New Issue
Block a user