Skip to content

Commit

Permalink
Feat/check contact request allowed (#267)
Browse files Browse the repository at this point in the history
* feat: check for contact request allowed

* fix: status code

* fix: mail template

* feat: reply to
  • Loading branch information
Jaszkowic authored Jun 18, 2024
1 parent 720a7bc commit 70155fd
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 103 deletions.
124 changes: 124 additions & 0 deletions supabase/functions/_shared/checks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { SupabaseClient } from "npm:@supabase/supabase-js";
import { sub } from "npm:date-fns";

export interface CheckResult {
isAllowed: boolean;
reason: string | undefined;
lookupData: ContactRequestLookupData | undefined;
}

export interface ContactRequestLookupData {
senderUsername: string;
senderEmail: string;
senderUserId: string;
recipientUserId: string;
}

export async function checkIfContactRequestIsAllowed(
recipientContactName: string,
token: string,
supabaseClient: SupabaseClient,
supabaseServiceRoleClient: SupabaseClient
): Promise<CheckResult> {
// Get the user (= sender) data from the token
const { data: senderData, error: senderDataError } =
await supabaseClient.auth.getUser(token);

console.log(senderData);

if (senderDataError) {
console.log(senderDataError);
return { isAllowed: false, reason: "unauthorized", lookupData: undefined };
}

// Lookup the sender username
const { data: senderLookupData, error: senderLookupDataError } =
await supabaseServiceRoleClient
.from("profiles")
.select("*")
.eq("id", senderData.user.id)
.single();

console.log(senderLookupData);

if (senderLookupDataError) {
console.log(senderLookupDataError);
return { isAllowed: false, reason: "not_found", lookupData: undefined };
}

// Lookup the recipient user id
const { data: recipientData, error: recipientDataError } =
await supabaseServiceRoleClient
.from("profiles")
.select("*")
.eq("username", recipientContactName)
.single();

if (recipientDataError) {
console.log(recipientDataError);
return { isAllowed: false, reason: "not_found", lookupData: undefined };
}

// Check if the user has already tried to contact the recipient
const { data: requestsToRecipient, error: requestsToRecipientError } =
await supabaseClient
.from("contact_requests")
.select("*")
.eq("user_id", senderData.user.id)
.eq("contact_id", recipientData.id)
.not("contact_mail_id", "is", null); // only count sent emails

if (requestsToRecipientError) {
console.log(requestsToRecipientError);
return {
isAllowed: false,
reason: "internal_server_error",
lookupData: undefined,
};
}

if (requestsToRecipient.length > 0) {
return {
isAllowed: false,
reason: "already_contacted_the_recipient_before",
lookupData: undefined,
};
}

// Check if the user has sent 3 contact requests in the last 24 hours
const { data: requestsOfLast24h, error: requestsOfLast24hError } =
await supabaseClient
.from("contact_requests")
.select("*")
.eq("user_id", senderData.user.id)
.not("contact_mail_id", "is", null) // only count sent emails
.gt("created_at", sub(new Date(), { days: 1 }).toISOString());

if (requestsOfLast24hError) {
console.log(requestsOfLast24hError);
return {
isAllowed: false,
reason: "internal_server_error",
lookupData: undefined,
};
}

if (requestsOfLast24h.length >= 3) {
return {
isAllowed: false,
reason: "already_sent_more_than_3_contact_requests",
lookupData: undefined,
};
}

return {
isAllowed: true,
reason: undefined,
lookupData: {
senderUsername: senderLookupData.username,
senderEmail: senderData.user.email,
senderUserId: senderData.user.id,
recipientUserId: recipientData.id,
},
};
}
67 changes: 67 additions & 0 deletions supabase/functions/check_contact_request/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
import { checkIfContactRequestIsAllowed } from "../_shared/checks.ts";
import { corsHeaders } from "../_shared/cors.ts";

const SUPABASE_URL = Deno.env.get("SUPABASE_URL");
const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY");
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");

const handler = async (_request: Request): Promise<Response> => {
if (_request.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders, status: 204 });
}

const { recipientContactName } = await _request.json();

const authHeader = _request.headers.get("Authorization")!;

const supabaseClient = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: { headers: { Authorization: authHeader } },
});

const supabaseServiceRoleClient = createClient(
SUPABASE_URL,
SUPABASE_SERVICE_ROLE_KEY
);

const token = authHeader.replace("Bearer ", "");

const { isAllowed, reason } = await checkIfContactRequestIsAllowed(
recipientContactName,
token,
supabaseClient,
supabaseServiceRoleClient
);

if (!isAllowed) {
return new Response(
JSON.stringify({
isContactRequestAllowed: false,
reason,
}),
{
status: 200, // We have to use 200 here to allow the client to read the response body
headers: {
...corsHeaders,
"Content-Type": "application/json",
},
}
);
}

return new Response(
JSON.stringify({
isContactRequestAllowed: true,
reason: undefined,
}),
{
status: 200,
headers: {
...corsHeaders,
"Content-Type": "application/json",
},
}
);
};

Deno.serve(handler);
116 changes: 17 additions & 99 deletions supabase/functions/submit_contact_request/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
import { sub } from "npm:date-fns";
import nodemailer from "npm:nodemailer";
import { checkIfContactRequestIsAllowed } from "../_shared/checks.ts";
import { corsHeaders } from "../_shared/cors.ts";
import { mailTemplate } from "./mail-template.ts";

Expand Down Expand Up @@ -35,106 +35,23 @@ const handler = async (_request: Request): Promise<Response> => {

// Get the user (= sender) data from the token
const token = authHeader.replace("Bearer ", "");
const { data: senderData, error: senderDataError } =
await supabaseClient.auth.getUser(token);

if (senderDataError) {
console.log(senderDataError);
return new Response(JSON.stringify({}), { status: 401 });
}

// Lookup the sender username
const { data: senderLookupData, error: senderLookupDataError } =
await supabaseServiceRoleClient
.from("profiles")
.select("*")
.eq("id", senderData.user.id)
.single();

if (senderLookupDataError) {
console.log(senderLookupDataError);
return new Response(JSON.stringify(senderLookupDataError), {
status: 404,
headers: corsHeaders,
});
}

// Lookup the recipient user id
const { data: recipientData, error: recipientDataError } =
await supabaseServiceRoleClient
.from("profiles")
.select("*")
.eq("username", recipientContactName)
.single();

if (recipientDataError) {
console.log(recipientDataError);
return new Response(JSON.stringify(recipientDataError), {
status: 404,
headers: corsHeaders,
});
}

// Check if the user has already tried to contact the recipient
const { data: requestsToRecipient, error: requestsToRecipientError } =
await supabaseClient
.from("contact_requests")
.select("*")
.eq("user_id", senderData.user.id)
.eq("contact_id", recipientData.id)
.not("contact_mail_id", "is", null); // only count sent emails

if (requestsToRecipientError) {
console.log(requestsToRecipientError);
return new Response(JSON.stringify(requestsToRecipientError), {
status: 500,
headers: corsHeaders,
});
}

if (requestsToRecipient.length > 0) {
return new Response(
JSON.stringify({
code: "already_contacted_the_recipient_before",
message:
"User has already sent a contact request to the recipient, not allowed to send another one.",
}),
{
status: 200,
headers: {
...corsHeaders,
"Content-Type": "application/json",
},
}
const { isAllowed, reason, lookupData } =
await checkIfContactRequestIsAllowed(
recipientContactName,
token,
supabaseClient,
supabaseServiceRoleClient
);
}

// Check if the user has sent 3 contact requests in the last 24 hours
const { data: requestsOfLast24h, error: requestsOfLast24hError } =
await supabaseClient
.from("contact_requests")
.select("*")
.eq("user_id", senderData.user.id)
.not("contact_mail_id", "is", null) // only count sent emails
.gt("created_at", sub(new Date(), { days: 1 }).toISOString());

if (requestsOfLast24hError) {
console.log(requestsOfLast24hError);
return new Response(JSON.stringify(requestsOfLast24hError), {
status: 500,
headers: corsHeaders,
});
}

if (requestsOfLast24h.length >= 3) {
if (!isAllowed || !lookupData) {
return new Response(
JSON.stringify({
code: "already_sent_more_than_3_contact_requests",
message:
"User has already sent more than 3 contact requests in the last 24 hours, not allowed to send another one.",
isContactRequestAllowed: false,
reason,
}),
{
status: 200,
status: 403,
headers: {
...corsHeaders,
"Content-Type": "application/json",
Expand All @@ -146,7 +63,7 @@ const handler = async (_request: Request): Promise<Response> => {
// Lookup the recipient email address via serviceRoleClient
const { data: fullRecipientData, error: fullRecipientDataError } =
await supabaseServiceRoleClient
.rpc("get_user_data_for_id", { u_id: recipientData.id })
.rpc("get_user_data_for_id", { u_id: lookupData.recipientUserId })
.select("email")
.single();

Expand All @@ -163,8 +80,8 @@ const handler = async (_request: Request): Promise<Response> => {
await supabaseClient
.from("contact_requests")
.insert({
user_id: senderData.user.id,
contact_id: recipientData.id,
user_id: lookupData.senderUserId,
contact_id: lookupData.recipientUserId,
contact_message: message,
})
.select("*")
Expand Down Expand Up @@ -198,11 +115,12 @@ const handler = async (_request: Request): Promise<Response> => {
const mailOptions = {
from: SMTP_FROM,
to: fullRecipientData.email,
replyTo: lookupData.senderEmail,
subject: "[Gieß den Kiez] Kontaktanfrage / Contact request",
html: mailTemplate(
senderLookupData.username,
lookupData.senderUsername,
message,
senderData.user.email
lookupData.senderEmail
),
};

Expand Down
8 changes: 4 additions & 4 deletions supabase/functions/submit_contact_request/mail-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export const mailTemplate = (
box-sizing: border-box;
"
>
Der Gieß den Kiez User <span style="margin-left: 5px; margin-right: 5px; font-weight: bold;">${username}</span> möchte sich
<span style="font-weight: bold;">${username}</span> möchte sich
mit Dir vernetzen und hat Dir folgende Nachricht gesendet:
<div
Expand Down Expand Up @@ -232,7 +232,7 @@ export const mailTemplate = (
box-sizing: border-box;
"
>
The Gieß den Kiez user <span style="margin-left: 5px; margin-right: 5px; font-weight: bold;">${username}</span> would like to connect with you
<span style="font-weight: bold;">${username}</span> would like to connect with you
and has sent you the following message:
<div
Expand All @@ -241,7 +241,7 @@ export const mailTemplate = (
${message}</div
>
To reply to the user by e-mail, please write to:
To reply to the user via e-mail, please write to:
<div style="margin-top: 25px; margin-bottom: 25px;"><a href="mailto:${email}" >${email}</a></div>
<i style="
Expand All @@ -253,7 +253,7 @@ export const mailTemplate = (
box-sizing: border-box;
color: #aeaeae;
font-size: 14px;
text-align: center;">If the contact request contains inappropriate content, we are very sorry. Please inform our team immediately via info@citylab-berlin.org.</i>
text-align: center;">We apologize if the contact request contains inappropriate content. Please notify our team immediately via info@citylab-berlin.org.</i>
</p>
<p
Expand Down

0 comments on commit 70155fd

Please sign in to comment.