Skip to content

Commit

Permalink
Implement a yahrzeit calendar search by email feature
Browse files Browse the repository at this point in the history
  • Loading branch information
mjradwin committed May 13, 2024
1 parent bdc8bd9 commit 2eb21ed
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 30 deletions.
4 changes: 3 additions & 1 deletion src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {shabbatBrowse} from './shabbat-browse.js';
import {shabbatJsLink} from './shabbat-link.js';
import {shortUrlRedir} from './shortUrlRedir.js';
import {yahrzeitApp} from './yahrzeit.js';
import {yahrzeitEmailSub, yahrzeitEmailVerify} from './yahrzeit-email.js';
import {yahrzeitEmailSub, yahrzeitEmailVerify, yahrzeitEmailSearch} from './yahrzeit-email.js';
import {getZmanim} from './zmanim.js';
import {hebrewDateCalc} from './calc.js';
import {omerApp} from './omerApp.js';
Expand Down Expand Up @@ -136,6 +136,8 @@ export function wwwRouter() {
return yahrzeitEmailSub(ctx);
} else if (rpath.startsWith('/yahrzeit/verify')) {
return yahrzeitEmailVerify(ctx);
} else if (rpath.startsWith('/yahrzeit/search')) {
return yahrzeitEmailSearch(ctx);
} else if (rpath.startsWith('/yahrzeit')) {
return yahrzeitApp(ctx);
} else if (rpath.startsWith('/holidays/')) {
Expand Down
56 changes: 56 additions & 0 deletions src/yahrzeit-email.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {getIpAddress} from './getIpAddress.js';
import {validateEmail, mySendMail, getImgOpenHtml} from './emailCommon.js';
import {getMaxYahrzeitId, summarizeAnniversaryTypes,
getYahrzeitIds,
makeCalendarTitle,
getYahrzeitDetailsFromDb, getYahrzeitDetailForId} from './yahrzeitCommon.js';
import {ulid} from 'ulid';
import {basename} from 'path';
Expand Down Expand Up @@ -103,6 +104,61 @@ function makeUlid(ctx) {
return id;
}

export async function yahrzeitEmailSearch(ctx) {
ctx.set('Cache-Control', 'private, max-age=0');
const q = Object.assign({}, ctx.request.body || {}, ctx.request.query);
if (!validateEmail(q.em)) {
ctx.throw(400, `Invalid email address ${q.em}`);
}
q.em = q.em.toLowerCase();
const sql = `SELECT e.id, e.calendar_id, y.contents
FROM yahrzeit_email e, yahrzeit y
WHERE e.email_addr = ? AND e.sub_status = 'active'
AND e.calendar_id = y.id`;
const results = await dbQuery(ctx, sql, [q.em]);
if (!results || !results[0]) {
ctx.status = 404;
return ctx.render('yahrzeit-search-notfound', {q});
}
const nonEmpty = results.filter((row) => getMaxYahrzeitId(row.contents) !== 0);
if (!nonEmpty.length) {
ctx.status = 404;
return ctx.render('yahrzeit-search-notfound', {q});
}
const ip = getIpAddress(ctx);
let html = `
<div dir="ltr" style="font-size:18px;font-family:georgia,'times new roman',times,serif;">
<div>Here are your current Yahrzeit + Anniversary Calendar email subscriptions
from Hebcal.com.</div>
<ul>
`;
for (const row of nonEmpty) {
const title = makeCalendarTitle(row.contents, 100);
const url = `https://www.hebcal.com/yahrzeit/edit/${row.calendar_id}?${UTM_PARAM}`;
html += `<li><a href="${url}">${title}</a>\n`;
}
html += `</ul>
${BLANK}
<div style="font-size:16px">Kol Tuv,
<br>Hebcal.com</div>
${BLANK}
<div style="font-size:11px;color:#999;font-family:arial,helvetica,sans-serif">
<div>This email was sent to ${q.em} by <a href="https://www.hebcal.com/?${UTM_PARAM}">Hebcal.com</a>.
Hebcal is a free Jewish calendar and holiday web site.</div>
${BLANK}
<div>[${ip}]</div>
</div>
</div>
`;
const message = {
to: q.em,
subject: `View your Yahrzeit + Anniversary Calendar subscriptions`,
html: html,
};
await mySendMail(ctx, message);
return ctx.render('yahrzeit-search-found', {q, results: nonEmpty});
}

export async function yahrzeitEmailSub(ctx) {
ctx.set('Cache-Control', 'private, max-age=0');
const q = Object.assign({}, ctx.request.body || {}, ctx.request.query);
Expand Down
30 changes: 1 addition & 29 deletions src/yahrzeit.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {getIpAddress} from './getIpAddress.js';
import {ulid} from 'ulid';
import {getMaxYahrzeitId, isNumKey, summarizeAnniversaryTypes,
getAnniversaryTypes,
getCalendarNames, makeCalendarTitle,
getYahrzeitDetailsFromDb, getYahrzeitDetailForId} from './yahrzeitCommon.js';
import {makeLogInfo} from './logger.js';
import {isDeepStrictEqual} from 'node:util';
Expand Down Expand Up @@ -503,35 +504,6 @@ export async function yahrzeitDownload(ctx) {
}
}

/**
* @param {any} query
* @param {number} maxlen
* @return {string}
*/
function makeCalendarTitle(query, maxlen) {
const names = getCalendarNames(query);
const calendarType = summarizeAnniversaryTypes(query, true);
let title = calendarType;
if (names.length > 0) {
title += ': ' + names.join(', ');
}
if (title.length > maxlen) {
title = title.substring(0, maxlen - 3) + '...';
}
return title;
}

/**
* @param {any} query
* @return {string[]}
*/
function getCalendarNames(query) {
return Object.entries(query)
.filter(([k, val]) => k[0] == 'n' && isNumKey(k))
.map((x) => x[1])
.filter((str) => str.length > 0);
}

/**
* @return {number}
*/
Expand Down
29 changes: 29 additions & 0 deletions src/yahrzeitCommon.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,32 @@ function getDateForId(query, id) {
const dd = query['d' + id];
return {yy, mm, dd};
}

/**
* @param {any} query
* @return {string[]}
*/
export function getCalendarNames(query) {
return Object.entries(query)
.filter(([k, val]) => k[0] == 'n' && isNumKey(k))
.map((x) => x[1])
.filter((str) => str.length > 0);
}

/**
* @param {any} query
* @param {number} maxlen
* @return {string}
*/
export function makeCalendarTitle(query, maxlen) {
const names = getCalendarNames(query);
const calendarType = summarizeAnniversaryTypes(query, true);
let title = calendarType;
if (names.length > 0) {
title += ': ' + names.join(', ');
}
if (title.length > maxlen) {
title = title.substring(0, maxlen - 3) + '...';
}
return title;
}
11 changes: 11 additions & 0 deletions views/yahrzeit-calpicker.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ calendar feed to Apple, Google, Outlook, and more.</p>
<li><a href="/yahrzeit/edit/<%=cal.id%>"><%=cal.title%></a>
<% }); -%>
</ol>
<p class="lead">Or, search for an existing calendar by email address</p>
<form action="/yahrzeit/search" method="post" class="row row-cols-lg-auto g-3 align-items-center mb-3">
<div class="col">
<label class="visually-hidden" for="em">Email address</label>
<input class="form-control" type="email" name="em" id="em" placeholder="[email protected]" value="" required>
</div>
<div class="col">
<button type="submit" class="btn btn-primary">Search</button>
</div>
</form>

<p class="lead">Or, create a new personal calendar</p>
<p><a class="btn btn-primary" href="/yahrzeit/new">New calendar &raquo;</a></p>
<p>Yahrzeit refers to the anniversary, according to the Hebrew calendar, of the
Expand Down
18 changes: 18 additions & 0 deletions views/yahrzeit-search-found.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<%- await include('partials/header-and-navbar.ejs', {
title: 'Yahrzeit + Anniversary Calendar Subscription Search - Hebcal',
}) -%>
<div class="row mt-2">
<div class="col">
<div class="float-start">
<svg width="64" height="64" class="me-3 mb-2"><use xlink:href="<%=cspriteHref%>#couple"></use></svg>
</div>
<p class="clearfix lead">Search for an existing Yahrzeit + Anniversary Calendar by email address.</p>
<div class="alert alert-success" role="alert">
<svg class="icon me-2"><use xlink:href="<%=spriteHref%>#bs-check-large"></use></svg>
<strong>Success!</strong> An email message has been sent to
<strong><%= q.em %></strong> containing <%= results.length %> personal calendar link(s).
<br>Click the link(s) within that message to view your personal calendars.
</div><!-- .alert -->
</div><!-- .col -->
</div><!-- .row -->
<%- await include('partials/footer.ejs') -%>
26 changes: 26 additions & 0 deletions views/yahrzeit-search-notfound.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<%- await include('partials/header-and-navbar.ejs', {
title: 'Yahrzeit + Anniversary Calendar Subscription Search - Hebcal',
}) -%>
<div class="row mt-2">
<div class="col">
<div class="float-start">
<svg width="64" height="64" class="me-3 mb-2"><use xlink:href="<%=cspriteHref%>#couple"></use></svg>
</div>
<p class="clearfix lead">Search for an existing Yahrzeit + Anniversary Calendar by email address.</p>
<div class="alert alert-warning" role="alert">
Sorry, the email address <strong><%= q.em %></strong> is not
subscribed to Yahrzeit + Anniversary Calendar annual email reminders.
</div><!-- .alert -->
<p class="mt-3">Try searching again for a different email address</p>
<form action="/yahrzeit/search" method="post" class="row row-cols-lg-auto g-3 align-items-center mb-3">
<div class="col">
<label class="visually-hidden" for="em">Email address</label>
<input class="form-control" type="email" name="em" id="em" placeholder="[email protected]" value="" required>
</div>
<div class="col">
<button type="submit" class="btn btn-primary">Search</button>
</div>
</form>
</div><!-- .col -->
</div><!-- .row -->
<%- await include('partials/footer.ejs') -%>

0 comments on commit 2eb21ed

Please sign in to comment.