Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staging to prod #270

Merged
merged 12 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions .github/workflows/deploy-to-supabase-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ jobs:
- run: |
supabase link --project-ref $PRODUCTION_PROJECT_ID
supabase db push
supabase functions deploy
1 change: 1 addition & 0 deletions .github/workflows/deploy-to-supabase-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ jobs:
- run: |
supabase link --project-ref $STAGING_PROJECT_ID
supabase db push
supabase functions deploy
5 changes: 2 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ jobs:
- run: supabase start
- run: npm ci
- run: npm run build --if-present
# Make sure to run tests in band and force exit to avoid hanging tests
# until we know where the open handles are
- run: npm run lint
- run: npm test -- --runInBand --forceExit
- run: supabase stop
- run: npm run lint

release:
name: semantic-release
needs: [test]
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ npm test

On CI the Supabase is started automagically. See [.github/workflows/tests.yml](.github/workflows/tests.yml)

To run the tests for the Supabase Edge Functions, execute locally:

```bash
cd giessdenkiez-de-postgres-api
docker run -p 1025:1025 mailhog/mailhog
supabase start
supabase functions serve --no-verify-jwt --env-file supabase/.env.test
deno test --allow-all supabase/functions/tests/submit-contact-request-tests.ts --env=supabase/.env.test
```

## Supabase

### Migrations and Types
Expand Down
51 changes: 51 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,48 @@ export type Json =
export type Database = {
public: {
Tables: {
contact_requests: {
Row: {
contact_id: string
contact_mail_id: string | null
contact_message: string | null
created_at: string
id: string
user_id: string
}
Insert: {
contact_id: string
contact_mail_id?: string | null
contact_message?: string | null
created_at?: string
id?: string
user_id: string
}
Update: {
contact_id?: string
contact_mail_id?: string | null
contact_message?: string | null
created_at?: string
id?: string
user_id?: string
}
Relationships: [
{
foreignKeyName: "contact_requests_contact_id_fkey"
columns: ["contact_id"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
{
foreignKeyName: "contact_requests_user_id_fkey"
columns: ["user_id"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
]
}
profiles: {
Row: {
id: string
Expand Down Expand Up @@ -276,6 +318,15 @@ export type Database = {
}
Returns: number
}
get_user_data_for_id: {
Args: {
u_id: string
}
Returns: {
id: string
email: string
}[]
}
get_watered_and_adopted: {
Args: Record<PropertyKey, never>
Returns: {
Expand Down
9 changes: 9 additions & 0 deletions supabase/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
URL=http://host.docker.internal:54321
ANON_KEY=ey..
SERVICE_ROLE_KEY=ey...
ALLOWED_ORIGIN=http://localhost:5173
SMTP_HOST=...
SMTP_USER=...
SMTP_PASSWORD=...
SMTP_FROM=...
SMTP_PORT=...
7 changes: 7 additions & 0 deletions supabase/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALLOWED_ORIGIN=http://localhost:5173
SMTP_HOST=host.docker.internal
SMTP_USER=""
SMTP_PASSWORD=""
[email protected]
SMTP_PORT=1025
SMTP_SECURE=false
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we lo this?


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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log this?


if (senderLookupDataError) {
console.log(senderLookupDataError);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.error

return { isAllowed: false, reason: "not_found", lookupData: undefined };
}

// Lookup the recipient user id
const { data: recipientData, error: recipientDataError } =
await supabaseServiceRoleClient
.from("profiles")
.select("*")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we be more sparse which fields we 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.error?

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.error

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,
},
};
}
8 changes: 8 additions & 0 deletions supabase/functions/_shared/cors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const ALLOWED_ORIGIN = Deno.env.get("ALLOWED_ORIGIN");

export const corsHeaders = {
"Access-Control-Allow-Origin": ALLOWED_ORIGIN,
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers":
"Content-Type,Authorization,x-client-info,apikey",
};
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");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No check if these vars don't exisit

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implemented loadEnvVars to check for that

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> => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No error handling at all?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why _request?

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);
Loading
Loading