mirror of
https://github.com/feeddeck/feeddeck.git
synced 2026-05-10 15:03:49 -05:00
Update Deno Version to 1.45.2 (#242)
* Update Deno Version to 1.45.2
Update Deno Version in the Docker image to version 1.45.2 and update the
`@supabase/supabase-js` dependency specified in the `import_map.json`
file.
**NOTES:**
- The current recommended approach for managing dependencies is using a
`deno.json` file, which is currently not working locally and returns
the following error:
```
serving the request with supabase/functions/add-or-update-source-v1
worker boot error: failed to create the graph: Relative import path "@supabase/supabase-js" not prefixed with / or ./ or ../
at file:///Users/ricoberger/Documents/GitHub/feeddeck/feeddeck/supabase/functions/add-or-update-source-v1/index.ts:1:30
worker boot error: failed to create the graph: Relative import path "@supabase/supabase-js" not prefixed with / or ./ or ../
at file:///Users/ricoberger/Documents/GitHub/feeddeck/feeddeck/supabase/functions/add-or-update-source-v1/index.ts:1:30
InvalidWorkerCreation: worker boot error: failed to create the graph: Relative import path "@supabase/supabase-js" not prefixed with / or ./ or ../
at file:///Users/ricoberger/Documents/GitHub/feeddeck/feeddeck/supabase/functions/add-or-update-source-v1/index.ts:1:30
at async UserWorker.create (ext:sb_user_workers/user_workers.js:139:15)
at async Object.handler (file:///root/index.ts:157:22)
at async respond (ext:sb_core_main_js/js/http.js:197:14) {
name: "InvalidWorkerCreation"
}
```
- When using Deno v2 the dependencies via the `deno.json` file are
working. To enable Deno v2 the following must be set in the
`config.toml` file:
```
[edge_runtime]
deno_version = 2
```
- Deno v2 is currently only supported locally, see
https://supabase.com/blog/supabase-edge-functions-deploy-dashboard-deno-2-1#deno-21-preview
- Once Deno v2 is supported in the hosted platform, we should switch
from the `import_map.json` to `deno.json` to manage dependencies and
update the existing dependencies.
* Fix Errors
- Replace `err.toString()` with just `err` when logging errors.
- Add new `blobToFile` function, to upload files to the Supabase
storage, otherwise we receive the following error (https://github.com/feeddeck/feeddeck/actions/runs/14546547197/job/40812707871):
```
error: TS2345 [ERROR]: Argument of type 'Blob' is not assignable to parameter of type 'FileBody'.
Type 'Blob' is missing the following properties from type 'File': lastModified, name, webkitRelativePath
file,
~~~~
at file:///home/runner/work/feeddeck/feeddeck/supabase/functions/_shared/feed/utils/uploadFile.ts:66:9
```
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
# FROM denoland/deno:1.34.1
|
||||
FROM lukechannings/deno:v1.40.5
|
||||
FROM denoland/deno:1.45.2
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { runScheduler } from './scheduler/scheduler.ts';
|
||||
import { runWorker } from './worker/worker.ts';
|
||||
import { runTools } from './tools/tools.ts';
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import { runScheduler } from "./scheduler/scheduler.ts";
|
||||
import { runWorker } from "./worker/worker.ts";
|
||||
import { runTools } from "./tools/tools.ts";
|
||||
|
||||
/**
|
||||
* Next to the Supabase Edge functions we also have to create an command which
|
||||
@@ -9,32 +9,38 @@ import { runTools } from './tools/tools.ts';
|
||||
* scheduler or worker, to refetch the feeds for all user sources.
|
||||
*/
|
||||
const main = (args: string[]) => {
|
||||
if (args.length === 1 && args[0] === 'scheduler') {
|
||||
log('info', 'Start scheduler...');
|
||||
runScheduler().then(() => {
|
||||
Deno.exit(0);
|
||||
}).catch((err) => {
|
||||
log('error', 'Scheduler crashed', { error: err.toString() });
|
||||
Deno.exit(1);
|
||||
});
|
||||
} else if (args.length === 1 && args[0] === 'worker') {
|
||||
log('info', 'Start worker...');
|
||||
runWorker().then(() => {
|
||||
Deno.exit(0);
|
||||
}).catch((err) => {
|
||||
log('error', 'Worker crashed', { error: err.toString() });
|
||||
Deno.exit(1);
|
||||
});
|
||||
} else if (args.length >= 2 && args[0] === 'tools') {
|
||||
log('info', 'Start tools...');
|
||||
runTools(args).then(() => {
|
||||
Deno.exit(0);
|
||||
}).catch((err) => {
|
||||
log('error', 'Tools crashed', { error: err.toString() });
|
||||
Deno.exit(1);
|
||||
});
|
||||
if (args.length === 1 && args[0] === "scheduler") {
|
||||
log("info", "Start scheduler...");
|
||||
runScheduler()
|
||||
.then(() => {
|
||||
Deno.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
log("error", "Scheduler crashed", { error: err });
|
||||
Deno.exit(1);
|
||||
});
|
||||
} else if (args.length === 1 && args[0] === "worker") {
|
||||
log("info", "Start worker...");
|
||||
runWorker()
|
||||
.then(() => {
|
||||
Deno.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
log("error", "Worker crashed", { error: err });
|
||||
Deno.exit(1);
|
||||
});
|
||||
} else if (args.length >= 2 && args[0] === "tools") {
|
||||
log("info", "Start tools...");
|
||||
runTools(args)
|
||||
.then(() => {
|
||||
Deno.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
log("error", "Tools crashed", { error: err });
|
||||
Deno.exit(1);
|
||||
});
|
||||
} else {
|
||||
log('error', 'Invalid command-line arguments', { args: args });
|
||||
log("error", "Invalid command-line arguments", { args: args });
|
||||
Deno.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { connect, Redis } from 'redis';
|
||||
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
||||
import { connect, Redis } from "redis";
|
||||
import { createClient, SupabaseClient } from "@supabase/supabase-js";
|
||||
|
||||
import { log } from '../../_shared/utils/log.ts';
|
||||
import { log } from "../../_shared/utils/log.ts";
|
||||
import {
|
||||
FEEDDECK_REDIS_HOSTNAME,
|
||||
FEEDDECK_REDIS_PASSWORD,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
FEEDDECK_REDIS_USERNAME,
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../../_shared/utils/constants.ts';
|
||||
} from "../../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* `runScheduler` starts the scheduler which is responsible for fetching all
|
||||
@@ -78,11 +78,10 @@ const scheduleSources = async (
|
||||
* The `sourcesUpdatedAt` is used to fetch all sources which where not
|
||||
* updated in the last hour.
|
||||
*/
|
||||
const profileCreatedAt = Math.floor(new Date().getTime() / 1000) -
|
||||
(60 * 60 * 24 * 7);
|
||||
const sourcesUpdatedAt = Math.floor(new Date().getTime() / 1000) -
|
||||
(60 * 60);
|
||||
log('info', 'Schedule sources', {
|
||||
const profileCreatedAt =
|
||||
Math.floor(new Date().getTime() / 1000) - 60 * 60 * 24 * 7;
|
||||
const sourcesUpdatedAt = Math.floor(new Date().getTime() / 1000) - 60 * 60;
|
||||
log("info", "Schedule sources", {
|
||||
sourcesUpdatedAt: sourcesUpdatedAt,
|
||||
profileCreatedAt: profileCreatedAt,
|
||||
});
|
||||
@@ -98,19 +97,17 @@ const scheduleSources = async (
|
||||
let offset = 0;
|
||||
|
||||
while (true) {
|
||||
log('debug', 'Fetching profiles', { offset: offset });
|
||||
log("debug", "Fetching profiles", { offset: offset });
|
||||
|
||||
const { data: tmpProfiles, error: profilesError } = await supabaseClient
|
||||
.from(
|
||||
'profiles',
|
||||
).select('*').or(`tier.eq.premium,createdAt.gt.${profileCreatedAt}`)
|
||||
.order(
|
||||
'createdAt',
|
||||
)
|
||||
.from("profiles")
|
||||
.select("*")
|
||||
.or(`tier.eq.premium,createdAt.gt.${profileCreatedAt}`)
|
||||
.order("createdAt")
|
||||
.range(offset, offset + batchSize);
|
||||
if (profilesError) {
|
||||
log('error', 'Failed to get user profiles', {
|
||||
'error': profilesError,
|
||||
log("error", "Failed to get user profiles", {
|
||||
error: profilesError,
|
||||
});
|
||||
} else {
|
||||
profiles.push(...tmpProfiles);
|
||||
@@ -123,27 +120,25 @@ const scheduleSources = async (
|
||||
}
|
||||
}
|
||||
|
||||
log('info', 'Fetched profiles', { profilesCount: profiles.length });
|
||||
log("info", "Fetched profiles", { profilesCount: profiles.length });
|
||||
for (const profile of profiles) {
|
||||
/**
|
||||
* Fetch all sources for the current user profile which where not updated
|
||||
* in the last hour.
|
||||
*/
|
||||
const { data: sources, error: sourcesError } = await supabaseClient
|
||||
.from(
|
||||
'sources',
|
||||
).select('*').eq('userId', profile.id).lt(
|
||||
'updatedAt',
|
||||
sourcesUpdatedAt,
|
||||
);
|
||||
.from("sources")
|
||||
.select("*")
|
||||
.eq("userId", profile.id)
|
||||
.lt("updatedAt", sourcesUpdatedAt);
|
||||
if (sourcesError) {
|
||||
log('error', 'Failed to get user sources', {
|
||||
'profile': profile.id,
|
||||
'error': sourcesError,
|
||||
log("error", "Failed to get user sources", {
|
||||
profile: profile.id,
|
||||
error: sourcesError,
|
||||
});
|
||||
} else {
|
||||
log('info', 'Fetched sources', {
|
||||
'profile': profile.id,
|
||||
log("info", "Fetched sources", {
|
||||
profile: profile.id,
|
||||
sourcesCount: sources.length,
|
||||
});
|
||||
for (const source of sources) {
|
||||
@@ -152,7 +147,7 @@ const scheduleSources = async (
|
||||
* for updates anymore.
|
||||
* See https://github.com/zedeus/nitter/issues/1155#issuecomment-1913361757
|
||||
*/
|
||||
if (source.type === 'nitter') {
|
||||
if (source.type === "nitter") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -161,14 +156,14 @@ const scheduleSources = async (
|
||||
* was already updated in the last 24 hours. This is done to avoid
|
||||
* hitting the rate limits of the Reddit API.
|
||||
*/
|
||||
if (profile.tier === 'free' && source.type === 'reddit') {
|
||||
if (profile.tier === "free" && source.type === "reddit") {
|
||||
if (
|
||||
source.updatedAt > Math.floor(new Date().getTime() / 1000) -
|
||||
(60 * 60 * 24)
|
||||
source.updatedAt >
|
||||
Math.floor(new Date().getTime() / 1000) - 60 * 60 * 24
|
||||
) {
|
||||
log('debug', 'Skip source', {
|
||||
'source': source.id,
|
||||
'profile': profile.id,
|
||||
log("debug", "Skip source", {
|
||||
source: source.id,
|
||||
profile: profile.id,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -180,12 +175,12 @@ const scheduleSources = async (
|
||||
* we need the users account information to fetch the sources data,
|
||||
* e.g. the users GitHub token.
|
||||
*/
|
||||
log('info', 'Scheduling source', {
|
||||
'source': source.id,
|
||||
'profile': profile.id,
|
||||
log("info", "Scheduling source", {
|
||||
source: source.id,
|
||||
profile: profile.id,
|
||||
});
|
||||
await redisClient.rpush(
|
||||
'sources',
|
||||
"sources",
|
||||
JSON.stringify({
|
||||
source: source,
|
||||
profile: profile,
|
||||
@@ -195,6 +190,6 @@ const scheduleSources = async (
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log('error', 'Failed to schedule sources...', { error: err.toString() });
|
||||
log("error", "Failed to schedule sources...", { error: err });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { connect, Redis } from 'redis';
|
||||
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
||||
import { connect, Redis } from "redis";
|
||||
import { createClient, SupabaseClient } from "@supabase/supabase-js";
|
||||
|
||||
import { log } from '../../_shared/utils/log.ts';
|
||||
import { getFeed } from '../../_shared/feed/feed.ts';
|
||||
import { log } from "../../_shared/utils/log.ts";
|
||||
import { getFeed } from "../../_shared/feed/feed.ts";
|
||||
import {
|
||||
FEEDDECK_REDIS_HOSTNAME,
|
||||
FEEDDECK_REDIS_PASSWORD,
|
||||
FEEDDECK_REDIS_PORT,
|
||||
FEEDDECK_REDIS_USERNAME,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../../_shared/utils/constants.ts';
|
||||
import { FEEDDECK_SUPABASE_SERVICE_ROLE_KEY } from '../../_shared/utils/constants.ts';
|
||||
} from "../../_shared/utils/constants.ts";
|
||||
import { FEEDDECK_SUPABASE_SERVICE_ROLE_KEY } from "../../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* `runWorker` starts the worker which is responsible for fetching the feeds for
|
||||
@@ -63,14 +63,14 @@ const listenForSources = async (
|
||||
* Listen for new sources in the Redis queue. Once a valid source is
|
||||
* received we get the source and profile from the Redis data.
|
||||
*/
|
||||
const data = await redisClient.blpop(1000 * 60, 'sources');
|
||||
if (data && data[0] === 'sources') {
|
||||
const data = await redisClient.blpop(1000 * 60, "sources");
|
||||
if (data && data[0] === "sources") {
|
||||
const { source: redisSource, profile: redisProfile } = JSON.parse(
|
||||
data[1],
|
||||
);
|
||||
log('info', 'Received source', {
|
||||
'source': redisSource.id,
|
||||
'profile': redisProfile.id,
|
||||
log("info", "Received source", {
|
||||
source: redisSource.id,
|
||||
profile: redisProfile.id,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -93,36 +93,34 @@ const listenForSources = async (
|
||||
* sources and items.
|
||||
*/
|
||||
if (items.length > 0) {
|
||||
const { error: sourceError } = await supabaseClient.from('sources')
|
||||
.upsert(
|
||||
source,
|
||||
);
|
||||
const { error: sourceError } = await supabaseClient
|
||||
.from("sources")
|
||||
.upsert(source);
|
||||
if (sourceError) {
|
||||
log('error', 'Failed to save sources', { 'error': sourceError });
|
||||
log("error", "Failed to save sources", { error: sourceError });
|
||||
}
|
||||
|
||||
const { error: itemsError } = await supabaseClient.from('items')
|
||||
.upsert(
|
||||
items,
|
||||
);
|
||||
const { error: itemsError } = await supabaseClient
|
||||
.from("items")
|
||||
.upsert(items);
|
||||
if (itemsError) {
|
||||
log('error', 'Failed to save items', { 'error': itemsError });
|
||||
log("error", "Failed to save items", { error: itemsError });
|
||||
}
|
||||
|
||||
log('info', 'Updated source', {
|
||||
'source': redisSource.id,
|
||||
'profile': redisProfile.id,
|
||||
log("info", "Updated source", {
|
||||
source: redisSource.id,
|
||||
profile: redisProfile.id,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
log('error', 'Failed to fetch feed', {
|
||||
log("error", "Failed to fetch feed", {
|
||||
source: redisSource,
|
||||
'profile': redisProfile.id,
|
||||
'error': err.toString(),
|
||||
profile: redisProfile.id,
|
||||
error: err,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log('error', 'Failed to listen for sources', { 'error': err.toString() });
|
||||
log("error", "Failed to listen for sources", { error: err });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Feed, parseFeed } from 'rss';
|
||||
import { Feed, parseFeed } from "rss";
|
||||
|
||||
import { utils } from '../../utils/index.ts';
|
||||
import { feedutils } from './index.ts';
|
||||
import { ISource } from '../../models/source.ts';
|
||||
import { FEEDDECK_LOG_LEVEL } from '../../utils/constants.ts';
|
||||
import { utils } from "../../utils/index.ts";
|
||||
import { feedutils } from "./index.ts";
|
||||
import { ISource } from "../../models/source.ts";
|
||||
import { FEEDDECK_LOG_LEVEL } from "../../utils/constants.ts";
|
||||
|
||||
export const getAndParseFeed = async (
|
||||
requestUrl: string,
|
||||
@@ -16,7 +16,7 @@ export const getAndParseFeed = async (
|
||||
}
|
||||
|
||||
try {
|
||||
utils.log('debug', 'Get and parse feed', {
|
||||
utils.log("debug", "Get and parse feed", {
|
||||
sourceType: source.type,
|
||||
requestUrl: requestUrl,
|
||||
});
|
||||
@@ -24,10 +24,10 @@ export const getAndParseFeed = async (
|
||||
requestUrl,
|
||||
requestOptions
|
||||
? {
|
||||
...requestOptions,
|
||||
method: 'get',
|
||||
}
|
||||
: { method: 'get' },
|
||||
...requestOptions,
|
||||
method: "get",
|
||||
}
|
||||
: { method: "get" },
|
||||
5000,
|
||||
);
|
||||
|
||||
@@ -36,11 +36,11 @@ export const getAndParseFeed = async (
|
||||
if (err instanceof feedutils.FeedValidationError) {
|
||||
throw err;
|
||||
} else {
|
||||
utils.log('error', 'Failed to get feed', {
|
||||
utils.log("error", "Failed to get feed", {
|
||||
source: source,
|
||||
error: err.toString(),
|
||||
error: err,
|
||||
});
|
||||
throw new feedutils.FeedGetAndParseError('Failed to get feed');
|
||||
throw new feedutils.FeedGetAndParseError("Failed to get feed");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -56,17 +56,18 @@ const _parseFeed = async (
|
||||
const feed = await parseFeed(feedData);
|
||||
return feed;
|
||||
} catch (err) {
|
||||
utils.log('error', 'Failed to parse feed', {
|
||||
utils.log("error", "Failed to parse feed", {
|
||||
source: source,
|
||||
requestUrl: requestUrl,
|
||||
responseStatus: response.status,
|
||||
responseBody: FEEDDECK_LOG_LEVEL === 'debug' ? feedData : '',
|
||||
responseHeaders: FEEDDECK_LOG_LEVEL === 'debug'
|
||||
? Object.fromEntries(response.headers.entries())
|
||||
: '',
|
||||
error: err.toString(),
|
||||
responseBody: FEEDDECK_LOG_LEVEL === "debug" ? feedData : "",
|
||||
responseHeaders:
|
||||
FEEDDECK_LOG_LEVEL === "debug"
|
||||
? Object.fromEntries(response.headers.entries())
|
||||
: "",
|
||||
error: err,
|
||||
});
|
||||
throw new feedutils.FeedGetAndParseError('Failed to parse feed');
|
||||
throw new feedutils.FeedGetAndParseError("Failed to parse feed");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,11 +79,11 @@ const _parseFeedData = async (
|
||||
const feed = await parseFeed(feedData);
|
||||
return feed;
|
||||
} catch (err) {
|
||||
utils.log('error', 'Failed to parse feed', {
|
||||
utils.log("error", "Failed to parse feed", {
|
||||
source: source,
|
||||
feedData: FEEDDECK_LOG_LEVEL === 'debug' ? feedData : '',
|
||||
error: err.toString(),
|
||||
feedData: FEEDDECK_LOG_LEVEL === "debug" ? feedData : "",
|
||||
error: err,
|
||||
});
|
||||
throw new feedutils.FeedGetAndParseError('Failed to parse feed');
|
||||
throw new feedutils.FeedGetAndParseError("Failed to parse feed");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { SupabaseClient } from '@supabase/supabase-js';
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
|
||||
import { ISource } from '../../models/source.ts';
|
||||
import { fetchWithTimeout } from '../../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../../utils/log.ts';
|
||||
import { ISource } from "../../models/source.ts";
|
||||
import { fetchWithTimeout } from "../../utils/fetchWithTimeout.ts";
|
||||
import { log } from "../../utils/log.ts";
|
||||
|
||||
/**
|
||||
* `uploadSourceIcon` uploads the `icon` of the provided `source` to the
|
||||
@@ -16,8 +16,9 @@ export const uploadSourceIcon = async (
|
||||
source: ISource,
|
||||
): Promise<string | undefined> => {
|
||||
if (
|
||||
!source.icon || source.icon === '' || (!source.icon.startsWith('http://') &&
|
||||
!source.icon.startsWith('https://'))
|
||||
!source.icon ||
|
||||
source.icon === "" ||
|
||||
(!source.icon.startsWith("http://") && !source.icon.startsWith("https://"))
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -25,9 +26,9 @@ export const uploadSourceIcon = async (
|
||||
try {
|
||||
const cdnIcon = await uploadFile(
|
||||
supabaseClient,
|
||||
'sources',
|
||||
"sources",
|
||||
source.icon,
|
||||
`${source.userId}/${source.id}.${source.icon.split('.').pop()}`,
|
||||
`${source.userId}/${source.id}.${source.icon.split(".").pop()}`,
|
||||
);
|
||||
|
||||
if (cdnIcon) {
|
||||
@@ -54,30 +55,45 @@ const uploadFile = async (
|
||||
try {
|
||||
const fileResponse = await fetchWithTimeout(
|
||||
sourcePath,
|
||||
{ method: 'get' },
|
||||
{ method: "get" },
|
||||
5000,
|
||||
);
|
||||
const file = await fileResponse.blob();
|
||||
const blob = await fileResponse.blob();
|
||||
|
||||
const { data: uploadData, error: uploadError } = await supabaseClient
|
||||
.storage.from(bucket)
|
||||
.upload(
|
||||
targetPath,
|
||||
file,
|
||||
{
|
||||
upsert: true,
|
||||
},
|
||||
);
|
||||
const { data: uploadData, error: uploadError } =
|
||||
await supabaseClient.storage
|
||||
.from(bucket)
|
||||
.upload(
|
||||
targetPath,
|
||||
blobToFile(blob, targetPath.replace(/^.*[\\/]/, "")),
|
||||
{
|
||||
upsert: true,
|
||||
},
|
||||
);
|
||||
if (uploadError) {
|
||||
log('error', 'Failed to upload source icon', {
|
||||
'error': uploadError,
|
||||
log("error", "Failed to upload source icon", {
|
||||
error: uploadError,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return uploadData?.path;
|
||||
} catch (err) {
|
||||
log('error', 'Failed to upload source icon', { 'error': err.toString() });
|
||||
log("error", "Failed to upload source icon", { error: err });
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* `blobToFile` converts a Blob to a File. This is needed because the Supabase
|
||||
* client only accepts File objects for uploading files. The `File` object
|
||||
* will be created with the provided `fileName` and the current date as
|
||||
* last modified date.
|
||||
*/
|
||||
const blobToFile = (blob: Blob, fileName: string): File => {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const b: any = blob;
|
||||
b.lastModifiedDate = new Date();
|
||||
b.name = fileName;
|
||||
return blob as File;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
import { corsHeaders } from '../_shared/utils/cors.ts';
|
||||
import { getFeed } from '../_shared/feed/feed.ts';
|
||||
import { ISource } from '../_shared/models/source.ts';
|
||||
import { IProfile } from '../_shared/models/profile.ts';
|
||||
import { utils } from '../_shared/utils/index.ts';
|
||||
import { feedutils } from '../_shared/feed/utils/index.ts';
|
||||
import { corsHeaders } from "../_shared/utils/cors.ts";
|
||||
import { getFeed } from "../_shared/feed/feed.ts";
|
||||
import { ISource } from "../_shared/models/source.ts";
|
||||
import { IProfile } from "../_shared/models/profile.ts";
|
||||
import { utils } from "../_shared/utils/index.ts";
|
||||
import { feedutils } from "../_shared/feed/utils/index.ts";
|
||||
import {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* The `add-or-update-source-v1` edge function is used to add a new source and
|
||||
@@ -24,8 +24,8 @@ Deno.serve(async (req) => {
|
||||
* We need to handle the preflight request for CORS as it is described in the
|
||||
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
||||
*/
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -39,7 +39,7 @@ Deno.serve(async (req) => {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
@@ -51,12 +51,14 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* Get the user from the request. If there is no user, we return an error.
|
||||
*/
|
||||
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
||||
const {
|
||||
data: { user },
|
||||
} = await userSupabaseClient.auth.getUser();
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 401,
|
||||
});
|
||||
@@ -84,21 +86,20 @@ Deno.serve(async (req) => {
|
||||
* than one profile, we return an error.
|
||||
*/
|
||||
const { data: profile, error: profileError } = await adminSupabaseClient
|
||||
.from(
|
||||
'profiles',
|
||||
)
|
||||
.select('*').eq('id', user.id);
|
||||
.from("profiles")
|
||||
.select("*")
|
||||
.eq("id", user.id);
|
||||
if (profileError || profile?.length !== 1) {
|
||||
utils.log('error', 'Failed to get user profile', {
|
||||
'user': user,
|
||||
'error': profileError,
|
||||
utils.log("error", "Failed to get user profile", {
|
||||
user: user,
|
||||
error: profileError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to get user profile' }),
|
||||
JSON.stringify({ error: "Failed to get user profile" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
@@ -116,58 +117,53 @@ Deno.serve(async (req) => {
|
||||
* sources.
|
||||
*/
|
||||
const { count: sourcesCount, error: countError } = await adminSupabaseClient
|
||||
.from('sources')
|
||||
.select(
|
||||
'*',
|
||||
{ count: 'exact' },
|
||||
).eq('userId', user.id);
|
||||
.from("sources")
|
||||
.select("*", { count: "exact" })
|
||||
.eq("userId", user.id);
|
||||
if (countError || sourcesCount === null) {
|
||||
utils.log('error', 'Failed to get sources', { 'error': countError });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to get sources' }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
status: 500,
|
||||
utils.log("error", "Failed to get sources", { error: countError });
|
||||
return new Response(JSON.stringify({ error: "Failed to get sources" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
);
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
if (profile[0].tier === 'free' && sourcesCount >= 10) {
|
||||
if (profile[0].tier === "free" && sourcesCount >= 10) {
|
||||
utils.log(
|
||||
'warning',
|
||||
'User is on the free tier and has reached the maximum number of sources',
|
||||
"warning",
|
||||
"User is on the free tier and has reached the maximum number of sources",
|
||||
);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error:
|
||||
'You reached the maximum number of sources you can create, please upgrade your account to the premium tier.',
|
||||
"You reached the maximum number of sources you can create, please upgrade your account to the premium tier.",
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (profile[0].tier === 'premium' && sourcesCount >= 1000) {
|
||||
if (profile[0].tier === "premium" && sourcesCount >= 1000) {
|
||||
utils.log(
|
||||
'warning',
|
||||
'User is on the premium tier and has reached the maximum number of sources',
|
||||
"warning",
|
||||
"User is on the premium tier and has reached the maximum number of sources",
|
||||
);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'You reached the maximum number of sources you can create.',
|
||||
error: "You reached the maximum number of sources you can create.",
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
@@ -197,32 +193,30 @@ Deno.serve(async (req) => {
|
||||
* If there is an error, we return an error. If the insert succeeds we
|
||||
* return the source object.
|
||||
*/
|
||||
const { error: sourceError } = await adminSupabaseClient.from('sources')
|
||||
.insert(
|
||||
source,
|
||||
);
|
||||
const { error: sourceError } = await adminSupabaseClient
|
||||
.from("sources")
|
||||
.insert(source);
|
||||
if (sourceError) {
|
||||
utils.log('error', 'Failed to save sources', { 'error': sourceError });
|
||||
return new Response(JSON.stringify({ error: 'Failed to save sources' }), {
|
||||
utils.log("error", "Failed to save sources", { error: sourceError });
|
||||
return new Response(JSON.stringify({ error: "Failed to save sources" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
if (items.length > 0) {
|
||||
const { error: itemsError } = await adminSupabaseClient.from('items')
|
||||
.insert(
|
||||
items,
|
||||
);
|
||||
const { error: itemsError } = await adminSupabaseClient
|
||||
.from("items")
|
||||
.insert(items);
|
||||
if (itemsError) {
|
||||
utils.log('error', 'Failed to save items', { 'error': itemsError });
|
||||
return new Response(JSON.stringify({ error: 'Failed to save items' }), {
|
||||
utils.log("error", "Failed to save items", { error: itemsError });
|
||||
return new Response(JSON.stringify({ error: "Failed to save items" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
});
|
||||
@@ -232,49 +226,43 @@ Deno.serve(async (req) => {
|
||||
return new Response(JSON.stringify(source), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof feedutils.FeedValidationError) {
|
||||
utils.log('error', 'FeedValidationError', {
|
||||
'error': err.toString(),
|
||||
utils.log("error", "FeedValidationError", {
|
||||
error: err,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: err.message }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
status: 400,
|
||||
return new Response(JSON.stringify({ error: err.message }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
);
|
||||
status: 400,
|
||||
});
|
||||
} else if (err instanceof feedutils.FeedGetAndParseError) {
|
||||
utils.log('error', 'FeedGetAndParseError', {
|
||||
'error': err.toString(),
|
||||
utils.log("error", "FeedGetAndParseError", {
|
||||
error: err,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: err.message }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
status: 400,
|
||||
return new Response(JSON.stringify({ error: err.message }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
);
|
||||
status: 400,
|
||||
});
|
||||
} else {
|
||||
utils.log('error', 'An unexpected error occured', {
|
||||
'error': err.toString(),
|
||||
utils.log("error", "An unexpected error occured", {
|
||||
error: err,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'An unexpected error occured' }),
|
||||
JSON.stringify({ error: "An unexpected error occured" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
import { corsHeaders } from '../_shared/utils/cors.ts';
|
||||
import { getFeed } from '../_shared/feed/feed.ts';
|
||||
import { ISourceOptions, TSourceType } from '../_shared/models/source.ts';
|
||||
import { IProfile } from '../_shared/models/profile.ts';
|
||||
import { utils } from '../_shared/utils/index.ts';
|
||||
import { feedutils } from '../_shared/feed/utils/index.ts';
|
||||
import { corsHeaders } from "../_shared/utils/cors.ts";
|
||||
import { getFeed } from "../_shared/feed/feed.ts";
|
||||
import { ISourceOptions, TSourceType } from "../_shared/models/source.ts";
|
||||
import { IProfile } from "../_shared/models/profile.ts";
|
||||
import { utils } from "../_shared/utils/index.ts";
|
||||
import { feedutils } from "../_shared/feed/utils/index.ts";
|
||||
import {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* DEPRECATED: This function is deprecated and will be removed in the future.
|
||||
@@ -21,8 +21,8 @@ Deno.serve(async (req) => {
|
||||
* We need to handle the preflight request for CORS as it is described in the
|
||||
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
||||
*/
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -36,7 +36,7 @@ Deno.serve(async (req) => {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
@@ -48,12 +48,14 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* Get the user from the request. If there is no user, we return an error.
|
||||
*/
|
||||
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
||||
const {
|
||||
data: { user },
|
||||
} = await userSupabaseClient.auth.getUser();
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 401,
|
||||
});
|
||||
@@ -81,21 +83,20 @@ Deno.serve(async (req) => {
|
||||
* than one profile, we return an error.
|
||||
*/
|
||||
const { data: profile, error: profileError } = await adminSupabaseClient
|
||||
.from(
|
||||
'profiles',
|
||||
)
|
||||
.select('*').eq('id', user.id);
|
||||
.from("profiles")
|
||||
.select("*")
|
||||
.eq("id", user.id);
|
||||
if (profileError || profile?.length !== 1) {
|
||||
utils.log('error', 'Failed to get user profile', {
|
||||
'user': user,
|
||||
'error': profileError,
|
||||
utils.log("error", "Failed to get user profile", {
|
||||
user: user,
|
||||
error: profileError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to get user profile' }),
|
||||
JSON.stringify({ error: "Failed to get user profile" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
@@ -113,58 +114,53 @@ Deno.serve(async (req) => {
|
||||
* sources.
|
||||
*/
|
||||
const { count: sourcesCount, error: countError } = await adminSupabaseClient
|
||||
.from('sources')
|
||||
.select(
|
||||
'*',
|
||||
{ count: 'exact' },
|
||||
).eq('userId', user.id);
|
||||
.from("sources")
|
||||
.select("*", { count: "exact" })
|
||||
.eq("userId", user.id);
|
||||
if (countError || sourcesCount === null) {
|
||||
utils.log('error', 'Failed to get sources', { 'error': countError });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to get sources' }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
status: 500,
|
||||
utils.log("error", "Failed to get sources", { error: countError });
|
||||
return new Response(JSON.stringify({ error: "Failed to get sources" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
);
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
if (profile[0].tier === 'free' && sourcesCount >= 10) {
|
||||
if (profile[0].tier === "free" && sourcesCount >= 10) {
|
||||
utils.log(
|
||||
'warning',
|
||||
'User is on the free tier and has reached the maximum number of sources',
|
||||
"warning",
|
||||
"User is on the free tier and has reached the maximum number of sources",
|
||||
);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error:
|
||||
'You reached the maximum number of sources you can create, please upgrade your account to the premium tier.',
|
||||
"You reached the maximum number of sources you can create, please upgrade your account to the premium tier.",
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (profile[0].tier === 'premium' && sourcesCount >= 1000) {
|
||||
if (profile[0].tier === "premium" && sourcesCount >= 1000) {
|
||||
utils.log(
|
||||
'warning',
|
||||
'User is on the premium tier and has reached the maximum number of sources',
|
||||
"warning",
|
||||
"User is on the premium tier and has reached the maximum number of sources",
|
||||
);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'You reached the maximum number of sources you can create.',
|
||||
error: "You reached the maximum number of sources you can create.",
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
@@ -175,7 +171,11 @@ Deno.serve(async (req) => {
|
||||
* Get the column, source typ and options from the request. Based on this
|
||||
* information, we get the source and items from the `getFeed` function.
|
||||
*/
|
||||
const { columnId, type, options }: {
|
||||
const {
|
||||
columnId,
|
||||
type,
|
||||
options,
|
||||
}: {
|
||||
columnId: string;
|
||||
type: TSourceType;
|
||||
options: ISourceOptions;
|
||||
@@ -186,12 +186,12 @@ Deno.serve(async (req) => {
|
||||
undefined,
|
||||
profile[0] as IProfile,
|
||||
{
|
||||
id: '',
|
||||
id: "",
|
||||
userId: user.id,
|
||||
columnId: columnId,
|
||||
type: type,
|
||||
options: options,
|
||||
title: '',
|
||||
title: "",
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
@@ -204,32 +204,30 @@ Deno.serve(async (req) => {
|
||||
* If there is an error, we return an error. If the insert succeeds we
|
||||
* return the source object.
|
||||
*/
|
||||
const { error: sourceError } = await adminSupabaseClient.from('sources')
|
||||
.insert(
|
||||
source,
|
||||
);
|
||||
const { error: sourceError } = await adminSupabaseClient
|
||||
.from("sources")
|
||||
.insert(source);
|
||||
if (sourceError) {
|
||||
utils.log('error', 'Failed to save sources', { 'error': sourceError });
|
||||
return new Response(JSON.stringify({ error: 'Failed to save sources' }), {
|
||||
utils.log("error", "Failed to save sources", { error: sourceError });
|
||||
return new Response(JSON.stringify({ error: "Failed to save sources" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
if (items.length > 0) {
|
||||
const { error: itemsError } = await adminSupabaseClient.from('items')
|
||||
.insert(
|
||||
items,
|
||||
);
|
||||
const { error: itemsError } = await adminSupabaseClient
|
||||
.from("items")
|
||||
.insert(items);
|
||||
if (itemsError) {
|
||||
utils.log('error', 'Failed to save items', { 'error': itemsError });
|
||||
return new Response(JSON.stringify({ error: 'Failed to save items' }), {
|
||||
utils.log("error", "Failed to save items", { error: itemsError });
|
||||
return new Response(JSON.stringify({ error: "Failed to save items" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
});
|
||||
@@ -239,49 +237,43 @@ Deno.serve(async (req) => {
|
||||
return new Response(JSON.stringify(source), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof feedutils.FeedValidationError) {
|
||||
utils.log('error', 'FeedValidationError', {
|
||||
'error': err.toString(),
|
||||
utils.log("error", "FeedValidationError", {
|
||||
error: err,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: err.message }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
status: 400,
|
||||
return new Response(JSON.stringify({ error: err.message }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
);
|
||||
status: 400,
|
||||
});
|
||||
} else if (err instanceof feedutils.FeedGetAndParseError) {
|
||||
utils.log('error', 'FeedGetAndParseError', {
|
||||
'error': err.toString(),
|
||||
utils.log("error", "FeedGetAndParseError", {
|
||||
error: err,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: err.message }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
status: 400,
|
||||
return new Response(JSON.stringify({ error: err.message }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
);
|
||||
status: 400,
|
||||
});
|
||||
} else {
|
||||
utils.log('error', 'An unexpected error occured', {
|
||||
'error': err.toString(),
|
||||
utils.log("error", "An unexpected error occured", {
|
||||
error: err,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'An unexpected error occured' }),
|
||||
JSON.stringify({ error: "An unexpected error occured" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
import { corsHeaders } from '../_shared/utils/cors.ts';
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { corsHeaders } from "../_shared/utils/cors.ts";
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* The `delete-user-v1` edge function is used to delete the current user. When
|
||||
@@ -18,8 +18,8 @@ Deno.serve(async (req) => {
|
||||
* We need to handle the preflight request for CORS as it is described in the
|
||||
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
||||
*/
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -33,7 +33,7 @@ Deno.serve(async (req) => {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
@@ -45,12 +45,14 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* Get the user from the request. If there is no user, we return an error.
|
||||
*/
|
||||
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
||||
const {
|
||||
data: { user },
|
||||
} = await userSupabaseClient.auth.getUser();
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 401,
|
||||
});
|
||||
@@ -80,36 +82,35 @@ Deno.serve(async (req) => {
|
||||
* account is already deleted.
|
||||
*/
|
||||
const { data: profile, error: profileError } = await adminSupabaseClient
|
||||
.from(
|
||||
'profiles',
|
||||
)
|
||||
.select('*').eq('id', user.id);
|
||||
.from("profiles")
|
||||
.select("*")
|
||||
.eq("id", user.id);
|
||||
if (profileError || profile?.length !== 1) {
|
||||
log('error', 'Failed to get user profile', {
|
||||
'user': user,
|
||||
'error': profileError,
|
||||
log("error", "Failed to get user profile", {
|
||||
user: user,
|
||||
error: profileError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to get user profile' }),
|
||||
JSON.stringify({ error: "Failed to get user profile" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (profile[0].tier !== 'free') {
|
||||
if (profile[0].tier !== "free") {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error:
|
||||
'User can not be deleted, because of an active subscription, please cancel your subscription first',
|
||||
"User can not be deleted, because of an active subscription, please cancel your subscription first",
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
@@ -121,19 +122,19 @@ Deno.serve(async (req) => {
|
||||
* return an error. If the user was deleted successfully we return a 204
|
||||
* response.
|
||||
*/
|
||||
const { error: deleteError } = await adminSupabaseClient.auth.admin
|
||||
.deleteUser(user.id);
|
||||
const { error: deleteError } =
|
||||
await adminSupabaseClient.auth.admin.deleteUser(user.id);
|
||||
if (deleteError) {
|
||||
log('error', 'Failed to get delete user', {
|
||||
'user': user,
|
||||
'error': deleteError,
|
||||
log("error", "Failed to get delete user", {
|
||||
user: user,
|
||||
error: deleteError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to get delete user' }),
|
||||
JSON.stringify({ error: "Failed to get delete user" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
@@ -143,18 +144,18 @@ Deno.serve(async (req) => {
|
||||
return new Response(undefined, {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
||||
log("error", "An unexpected error occured", { error: err });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'An unexpected error occured' }),
|
||||
JSON.stringify({ error: "An unexpected error occured" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
import { corsHeaders } from '../_shared/utils/cors.ts';
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { corsHeaders } from "../_shared/utils/cors.ts";
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* The `generate-magic-link-v1` edge function is used to generate a magic link
|
||||
@@ -17,8 +17,8 @@ Deno.serve(async (req) => {
|
||||
* We need to handle the preflight request for CORS as it is described in the
|
||||
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
||||
*/
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -32,7 +32,7 @@ Deno.serve(async (req) => {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
@@ -44,12 +44,14 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* Get the user from the request. If there is no user, we return an error.
|
||||
*/
|
||||
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
||||
const {
|
||||
data: { user },
|
||||
} = await userSupabaseClient.auth.getUser();
|
||||
if (!user || !user.email) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 401,
|
||||
});
|
||||
@@ -71,22 +73,22 @@ Deno.serve(async (req) => {
|
||||
},
|
||||
);
|
||||
|
||||
const { data: linkData, error: linkError } = await adminSupabaseClient.auth
|
||||
.admin.generateLink({
|
||||
type: 'magiclink',
|
||||
const { data: linkData, error: linkError } =
|
||||
await adminSupabaseClient.auth.admin.generateLink({
|
||||
type: "magiclink",
|
||||
email: user.email,
|
||||
});
|
||||
if (linkError) {
|
||||
log('error', 'Failed to generate magic link', {
|
||||
'user': user,
|
||||
'error': linkError,
|
||||
log("error", "Failed to generate magic link", {
|
||||
user: user,
|
||||
error: linkError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to generate magic link' }),
|
||||
JSON.stringify({ error: "Failed to generate magic link" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
@@ -98,19 +100,19 @@ Deno.serve(async (req) => {
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 200,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
||||
log("error", "An unexpected error occured", { error: err });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'An unexpected error occured' }),
|
||||
JSON.stringify({ error: "An unexpected error occured" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"std/testing/mock": "https://deno.land/std@0.208.0/testing/mock.ts",
|
||||
"std/hex": "https://deno.land/std@0.208.0/encoding/hex.ts",
|
||||
"std/crypto": "https://deno.land/std@0.208.0/crypto/mod.ts",
|
||||
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2.39.0",
|
||||
"@supabase/supabase-js": "jsr:@supabase/supabase-js@2",
|
||||
"rss": "https://deno.land/x/rss@1.0.0/mod.ts",
|
||||
"rss/types": "https://deno.land/x/rss@1.0.0/src/types/mod.ts",
|
||||
"cheerio": "https://esm.sh/cheerio@1.0.0-rc.12",
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
import { corsHeaders } from '../_shared/utils/cors.ts';
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { IProfile } from '../_shared/models/profile.ts';
|
||||
import { TSourceType } from '../_shared/models/source.ts';
|
||||
import { encrypt } from '../_shared/utils/encrypt.ts';
|
||||
import { corsHeaders } from "../_shared/utils/cors.ts";
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import { IProfile } from "../_shared/models/profile.ts";
|
||||
import { TSourceType } from "../_shared/models/source.ts";
|
||||
import { encrypt } from "../_shared/utils/encrypt.ts";
|
||||
import {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* DEPRECATED: This function is deprecated and will be removed in the future.
|
||||
@@ -20,8 +20,8 @@ Deno.serve(async (req) => {
|
||||
* We need to handle the preflight request for CORS as it is described in the
|
||||
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
||||
*/
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -35,7 +35,7 @@ Deno.serve(async (req) => {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
@@ -47,12 +47,14 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* Get the user from the request. If there is no user, we return an error.
|
||||
*/
|
||||
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
||||
const {
|
||||
data: { user },
|
||||
} = await userSupabaseClient.auth.getUser();
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 401,
|
||||
});
|
||||
@@ -81,26 +83,22 @@ Deno.serve(async (req) => {
|
||||
* convert them to `true` or `false`, which indicates if the account is
|
||||
* connected or not.
|
||||
*/
|
||||
if (req.method === 'GET') {
|
||||
if (req.method === "GET") {
|
||||
const { data: profile, error: profileError } = await adminSupabaseClient
|
||||
.from(
|
||||
'profiles',
|
||||
)
|
||||
.select('*').eq(
|
||||
'id',
|
||||
user.id,
|
||||
);
|
||||
.from("profiles")
|
||||
.select("*")
|
||||
.eq("id", user.id);
|
||||
if (profileError || profile?.length !== 1) {
|
||||
log('error', 'Failed to get user profile', {
|
||||
'user': user,
|
||||
'error': profileError,
|
||||
log("error", "Failed to get user profile", {
|
||||
user: user,
|
||||
error: profileError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to get delete user' }),
|
||||
JSON.stringify({ error: "Failed to get delete user" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
@@ -109,19 +107,19 @@ Deno.serve(async (req) => {
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
'id': (profile[0] as IProfile).id,
|
||||
'tier': (profile[0] as IProfile).tier,
|
||||
'subscriptionProvider': (profile[0] as IProfile).subscriptionProvider,
|
||||
'accountGithub': (profile[0] as IProfile).accountGithub?.token
|
||||
id: (profile[0] as IProfile).id,
|
||||
tier: (profile[0] as IProfile).tier,
|
||||
subscriptionProvider: (profile[0] as IProfile).subscriptionProvider,
|
||||
accountGithub: (profile[0] as IProfile).accountGithub?.token
|
||||
? true
|
||||
: false,
|
||||
'createdAt': (profile[0] as IProfile).createdAt,
|
||||
'updatedAt': (profile[0] as IProfile).updatedAt,
|
||||
createdAt: (profile[0] as IProfile).createdAt,
|
||||
updatedAt: (profile[0] as IProfile).updatedAt,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 200,
|
||||
},
|
||||
@@ -136,7 +134,7 @@ Deno.serve(async (req) => {
|
||||
* process, e.g. setting a verify token and returning an url for
|
||||
* authentication.
|
||||
*/
|
||||
if (req.method === 'POST') {
|
||||
if (req.method === "POST") {
|
||||
const data: {
|
||||
action?: string;
|
||||
sourceType?: TSourceType;
|
||||
@@ -152,40 +150,39 @@ Deno.serve(async (req) => {
|
||||
* account to the users profile.
|
||||
*/
|
||||
if (
|
||||
data.action === 'add-account' && data.sourceType === 'github' &&
|
||||
data.action === "add-account" &&
|
||||
data.sourceType === "github" &&
|
||||
data.options?.token
|
||||
) {
|
||||
const { error: updateError } = await adminSupabaseClient.from(
|
||||
'profiles',
|
||||
).update({
|
||||
'accountGithub': { token: await encrypt(data.options.token) },
|
||||
}).eq('id', user.id);
|
||||
const { error: updateError } = await adminSupabaseClient
|
||||
.from("profiles")
|
||||
.update({
|
||||
accountGithub: { token: await encrypt(data.options.token) },
|
||||
})
|
||||
.eq("id", user.id);
|
||||
if (updateError) {
|
||||
log('error', 'Failed to update user profile', {
|
||||
'user': user,
|
||||
'error': updateError,
|
||||
log("error", "Failed to update user profile", {
|
||||
user: user,
|
||||
error: updateError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to update profile' }),
|
||||
JSON.stringify({ error: "Failed to update profile" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
}
|
||||
return new Response(
|
||||
undefined,
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
status: 200,
|
||||
return new Response(undefined, {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
);
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,52 +191,50 @@ Deno.serve(async (req) => {
|
||||
* the users GitHub account from his profile by setting the value of the
|
||||
* `accountGithub` column to `null`.
|
||||
*/
|
||||
if (data.action === 'delete-account' && data.sourceType === 'github') {
|
||||
const { error: updateError } = await adminSupabaseClient.from(
|
||||
'profiles',
|
||||
).update({
|
||||
'accountGithub': null,
|
||||
}).eq('id', user.id);
|
||||
if (data.action === "delete-account" && data.sourceType === "github") {
|
||||
const { error: updateError } = await adminSupabaseClient
|
||||
.from("profiles")
|
||||
.update({
|
||||
accountGithub: null,
|
||||
})
|
||||
.eq("id", user.id);
|
||||
if (updateError) {
|
||||
log('error', 'Failed to update user profile', {
|
||||
'user': user,
|
||||
'error': updateError,
|
||||
log("error", "Failed to update user profile", {
|
||||
user: user,
|
||||
error: updateError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to update profile' }),
|
||||
JSON.stringify({ error: "Failed to update profile" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
}
|
||||
return new Response(
|
||||
undefined,
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
status: 200,
|
||||
return new Response(undefined, {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
);
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the request data doesn't match any of the above conditions, we
|
||||
* return an error.
|
||||
*/
|
||||
log('error', 'Invalid request data', {
|
||||
'user': user,
|
||||
'request': data,
|
||||
log("error", "Invalid request data", {
|
||||
user: user,
|
||||
request: data,
|
||||
});
|
||||
return new Response(JSON.stringify({ error: 'Invalid request data' }), {
|
||||
return new Response(JSON.stringify({ error: "Invalid request data" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
});
|
||||
@@ -248,21 +243,21 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* If the request method is not GET, POST or DELETE, we return an error.
|
||||
*/
|
||||
return new Response(JSON.stringify({ error: 'Method Not Allowed' }), {
|
||||
return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 405,
|
||||
});
|
||||
} catch (err) {
|
||||
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
||||
log("error", "An unexpected error occured", { error: err });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'An unexpected error occured' }),
|
||||
JSON.stringify({ error: "An unexpected error occured" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { createClient, SupabaseClient, User } from '@supabase/supabase-js';
|
||||
import { createClient, SupabaseClient, User } from "@supabase/supabase-js";
|
||||
|
||||
import { corsHeaders } from '../_shared/utils/cors.ts';
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { IProfile } from '../_shared/models/profile.ts';
|
||||
import { corsHeaders } from "../_shared/utils/cors.ts";
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import { IProfile } from "../_shared/models/profile.ts";
|
||||
import {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
import { githubAddAccount, githubDeleteAccount } from './github.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
import { githubAddAccount, githubDeleteAccount } from "./github.ts";
|
||||
|
||||
/**
|
||||
* `getProfile` returns the users profile. The user profile contains information
|
||||
@@ -22,24 +22,20 @@ const getProfile = async (
|
||||
user: User,
|
||||
): Promise<Response> => {
|
||||
const { data: profile, error: profileError } = await supabaseClient
|
||||
.from(
|
||||
'profiles',
|
||||
)
|
||||
.select('*').eq(
|
||||
'id',
|
||||
user.id,
|
||||
);
|
||||
.from("profiles")
|
||||
.select("*")
|
||||
.eq("id", user.id);
|
||||
if (profileError || profile?.length !== 1) {
|
||||
log('error', 'Failed to get user profile', {
|
||||
'user': user,
|
||||
'error': profileError,
|
||||
log("error", "Failed to get user profile", {
|
||||
user: user,
|
||||
error: profileError,
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Failed to get delete user' }),
|
||||
JSON.stringify({ error: "Failed to get delete user" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 500,
|
||||
},
|
||||
@@ -48,19 +44,19 @@ const getProfile = async (
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
'id': (profile[0] as IProfile).id,
|
||||
'tier': (profile[0] as IProfile).tier,
|
||||
'subscriptionProvider': (profile[0] as IProfile).subscriptionProvider,
|
||||
'accountGithub': (profile[0] as IProfile).accountGithub?.token
|
||||
id: (profile[0] as IProfile).id,
|
||||
tier: (profile[0] as IProfile).tier,
|
||||
subscriptionProvider: (profile[0] as IProfile).subscriptionProvider,
|
||||
accountGithub: (profile[0] as IProfile).accountGithub?.token
|
||||
? true
|
||||
: false,
|
||||
'createdAt': (profile[0] as IProfile).createdAt,
|
||||
'updatedAt': (profile[0] as IProfile).updatedAt,
|
||||
createdAt: (profile[0] as IProfile).createdAt,
|
||||
updatedAt: (profile[0] as IProfile).updatedAt,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 200,
|
||||
},
|
||||
@@ -79,8 +75,8 @@ Deno.serve(async (req) => {
|
||||
* We need to handle the preflight request for CORS as it is described in the
|
||||
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
||||
*/
|
||||
if (method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
if (method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -94,7 +90,7 @@ Deno.serve(async (req) => {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
@@ -106,12 +102,14 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* Get the user from the request. If there is no user, we return an error.
|
||||
*/
|
||||
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
||||
const {
|
||||
data: { user },
|
||||
} = await userSupabaseClient.auth.getUser();
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 401,
|
||||
});
|
||||
@@ -139,16 +137,16 @@ Deno.serve(async (req) => {
|
||||
* multiple endpoints. If the request method is `POST` we also parse the
|
||||
* request body.
|
||||
*/
|
||||
const urlPattern = new URLPattern({ pathname: '/profile-v2/:id' });
|
||||
const urlPattern = new URLPattern({ pathname: "/profile-v2/:id" });
|
||||
const matchingPath = urlPattern.exec(url);
|
||||
const id = matchingPath ? matchingPath.pathname.groups.id : null;
|
||||
|
||||
let data = null;
|
||||
if (method === 'POST') {
|
||||
if (method === "POST") {
|
||||
data = await req.json();
|
||||
}
|
||||
|
||||
log('debug', 'Request data', {
|
||||
log("debug", "Request data", {
|
||||
user: user,
|
||||
method: method,
|
||||
id: id,
|
||||
@@ -160,11 +158,11 @@ Deno.serve(async (req) => {
|
||||
* action we need to execute.
|
||||
*/
|
||||
switch (true) {
|
||||
case method === 'GET' && id === 'getProfile':
|
||||
case method === "GET" && id === "getProfile":
|
||||
return await getProfile(adminSupabaseClient, user);
|
||||
case method === 'POST' && id === 'githubAddAccount':
|
||||
case method === "POST" && id === "githubAddAccount":
|
||||
return await githubAddAccount(adminSupabaseClient, user, data);
|
||||
case method === 'DELETE' && id === 'githubDeleteAccount':
|
||||
case method === "DELETE" && id === "githubDeleteAccount":
|
||||
return await githubDeleteAccount(adminSupabaseClient, user);
|
||||
default:
|
||||
/**
|
||||
@@ -172,22 +170,22 @@ Deno.serve(async (req) => {
|
||||
* doesn't match the request method and id we return a `400 Bad Request`
|
||||
* error.
|
||||
*/
|
||||
return new Response(JSON.stringify({ error: 'Bad Request' }), {
|
||||
return new Response(JSON.stringify({ error: "Bad Request" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
||||
log("error", "An unexpected error occured", { error: err });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'An unexpected error occured' }),
|
||||
JSON.stringify({ error: "An unexpected error occured" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import {
|
||||
FEEDDECK_REVENUECAT_WEBHOOK_HEADER,
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* The `IEventPayload` interface represents the payload of a RevenueCat webhook
|
||||
@@ -29,13 +29,13 @@ interface IEvent {
|
||||
* header in RevenueCat.
|
||||
*/
|
||||
const isAuthorized = (req: Request): boolean => {
|
||||
const authorizationHeader = req.headers.get('Authorization');
|
||||
const authorizationHeader = req.headers.get("Authorization");
|
||||
|
||||
if (!authorizationHeader || !authorizationHeader.startsWith('Bearer ')) {
|
||||
if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const authToken = authorizationHeader.split('Bearer ')[1];
|
||||
const authToken = authorizationHeader.split("Bearer ")[1];
|
||||
if (authToken !== FEEDDECK_REVENUECAT_WEBHOOK_HEADER) {
|
||||
return false;
|
||||
}
|
||||
@@ -72,39 +72,40 @@ export const manageSubscriptionStatusChange = async (
|
||||
* one profile, we return an error.
|
||||
*/
|
||||
const { data: profile, error: profileError } = await adminSupabaseClient
|
||||
.from(
|
||||
'profiles',
|
||||
)
|
||||
.select('*').eq('id', userId);
|
||||
.from("profiles")
|
||||
.select("*")
|
||||
.eq("id", userId);
|
||||
if (profileError || profile?.length !== 1) {
|
||||
log('error', 'Failed to get user profile', {
|
||||
'userId': userId,
|
||||
'error': profileError,
|
||||
log("error", "Failed to get user profile", {
|
||||
userId: userId,
|
||||
error: profileError,
|
||||
});
|
||||
throw new Error('Failed to get user profile');
|
||||
throw new Error("Failed to get user profile");
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user is already on the correct tier, we return early.
|
||||
*/
|
||||
if (
|
||||
(profile[0].tier === 'free' && !isCreated) ||
|
||||
(profile[0].tier === 'premium' && isCreated)
|
||||
(profile[0].tier === "free" && !isCreated) ||
|
||||
(profile[0].tier === "premium" && isCreated)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { error: updateError } = await adminSupabaseClient.from('profiles')
|
||||
const { error: updateError } = await adminSupabaseClient
|
||||
.from("profiles")
|
||||
.update({
|
||||
tier: isCreated ? 'premium' : 'free',
|
||||
subscriptionProvider: 'revenuecat',
|
||||
}).eq('id', profile[0].id);
|
||||
tier: isCreated ? "premium" : "free",
|
||||
subscriptionProvider: "revenuecat",
|
||||
})
|
||||
.eq("id", profile[0].id);
|
||||
if (updateError) {
|
||||
log('error', 'Failed to update user profile with new tier value', {
|
||||
'userId': userId,
|
||||
'error': updateError,
|
||||
log("error", "Failed to update user profile with new tier value", {
|
||||
userId: userId,
|
||||
error: updateError,
|
||||
});
|
||||
throw new Error('Failed to update user profile with new tier value');
|
||||
throw new Error("Failed to update user profile with new tier value");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -120,29 +121,23 @@ Deno.serve(async (req) => {
|
||||
* is done because we only want to accept POST requests. If the request is
|
||||
* not authorized, we return a 401 Unauthorized error.
|
||||
*/
|
||||
if (req.method !== 'POST') {
|
||||
return new Response(
|
||||
'Forbidden',
|
||||
{
|
||||
status: 403,
|
||||
},
|
||||
);
|
||||
if (req.method !== "POST") {
|
||||
return new Response("Forbidden", {
|
||||
status: 403,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isAuthorized(req)) {
|
||||
return new Response(
|
||||
'Unauthorized',
|
||||
{
|
||||
status: 401,
|
||||
},
|
||||
);
|
||||
return new Response("Unauthorized", {
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the payload of the received webhook event.
|
||||
*/
|
||||
const payload = await req.json() as IEventPayload;
|
||||
log('debug', 'Received event', { 'event': payload.event });
|
||||
const payload = (await req.json()) as IEventPayload;
|
||||
log("debug", "Received event", { event: payload.event });
|
||||
|
||||
/**
|
||||
* If the event type is `INITIAL_PURCHASE`, `RENEWAL` or `UNCANCELLATION`,
|
||||
@@ -151,31 +146,28 @@ Deno.serve(async (req) => {
|
||||
* `free`. All other event types are ignored.
|
||||
*/
|
||||
if (
|
||||
payload.event.type === 'INITIAL_PURCHASE' ||
|
||||
payload.event.type === 'RENEWAL' ||
|
||||
payload.event.type === 'UNCANCELLATION'
|
||||
payload.event.type === "INITIAL_PURCHASE" ||
|
||||
payload.event.type === "RENEWAL" ||
|
||||
payload.event.type === "UNCANCELLATION"
|
||||
) {
|
||||
await manageSubscriptionStatusChange(payload.event.app_user_id, true);
|
||||
return new Response('ok', {
|
||||
return new Response("ok", {
|
||||
status: 200,
|
||||
});
|
||||
} else if (payload.event.type === 'EXPIRATION') {
|
||||
} else if (payload.event.type === "EXPIRATION") {
|
||||
await manageSubscriptionStatusChange(payload.event.app_user_id, false);
|
||||
return new Response('ok', {
|
||||
return new Response("ok", {
|
||||
status: 200,
|
||||
});
|
||||
} else {
|
||||
return new Response('ok', {
|
||||
return new Response("ok", {
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
||||
return new Response(
|
||||
'An unexpected error occured',
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
log("error", "An unexpected error occured", { error: err });
|
||||
return new Response("An unexpected error occured", {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
import { corsHeaders } from '../_shared/utils/cors.ts';
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { corsHeaders } from "../_shared/utils/cors.ts";
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import {
|
||||
createBillingPortalSession,
|
||||
createOrRetrieveCustomer,
|
||||
} from '../_shared/stripe/stripe.ts';
|
||||
} from "../_shared/stripe/stripe.ts";
|
||||
import {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* The `stripe-create-billing-portal-link-v1` edge function is used to create a
|
||||
@@ -20,8 +20,8 @@ Deno.serve(async (req) => {
|
||||
* We need to handle the preflight request for CORS as it is described in the
|
||||
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
||||
*/
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -30,7 +30,7 @@ Deno.serve(async (req) => {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
@@ -42,12 +42,14 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* Get the user from the request. If there is no user, we return an error.
|
||||
*/
|
||||
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
||||
const {
|
||||
data: { user },
|
||||
} = await userSupabaseClient.auth.getUser();
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 401,
|
||||
});
|
||||
@@ -62,18 +64,18 @@ Deno.serve(async (req) => {
|
||||
return new Response(JSON.stringify({ url: url }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
||||
log("error", "An unexpected error occured", { error: err });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'An unexpected error occured' }),
|
||||
JSON.stringify({ error: "An unexpected error occured" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
import { corsHeaders } from '../_shared/utils/cors.ts';
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { corsHeaders } from "../_shared/utils/cors.ts";
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import {
|
||||
createCheckoutSession,
|
||||
createOrRetrieveCustomer,
|
||||
} from '../_shared/stripe/stripe.ts';
|
||||
} from "../_shared/stripe/stripe.ts";
|
||||
import {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
FEEDDECK_SUPABASE_URL,
|
||||
} from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/utils/constants.ts";
|
||||
|
||||
/**
|
||||
* The `stripe-create-checkout-session-v1` edge function is used to create a new
|
||||
@@ -21,8 +21,8 @@ Deno.serve(async (req) => {
|
||||
* We need to handle the preflight request for CORS as it is described in the
|
||||
* Supabase documentation: https://supabase.com/docs/guides/functions/cors
|
||||
*/
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -31,7 +31,7 @@ Deno.serve(async (req) => {
|
||||
FEEDDECK_SUPABASE_ANON_KEY,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get('Authorization')! },
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
@@ -43,12 +43,14 @@ Deno.serve(async (req) => {
|
||||
/**
|
||||
* Get the user from the request. If there is no user, we return an error.
|
||||
*/
|
||||
const { data: { user } } = await userSupabaseClient.auth.getUser();
|
||||
const {
|
||||
data: { user },
|
||||
} = await userSupabaseClient.auth.getUser();
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 401,
|
||||
});
|
||||
@@ -63,18 +65,18 @@ Deno.serve(async (req) => {
|
||||
return new Response(JSON.stringify({ url: url }), {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
||||
log("error", "An unexpected error occured", { error: err });
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'An unexpected error occured' }),
|
||||
JSON.stringify({ error: "An unexpected error occured" }),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
status: 400,
|
||||
},
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import Stripe from 'stripe';
|
||||
import Stripe from "stripe";
|
||||
|
||||
import { log } from '../_shared/utils/log.ts';
|
||||
import { log } from "../_shared/utils/log.ts";
|
||||
import {
|
||||
cryptoProvider,
|
||||
manageSubscriptionStatusChange,
|
||||
stripe,
|
||||
} from '../_shared/stripe/stripe.ts';
|
||||
import { FEEDDECK_STRIPE_WEBHOOK_SIGNING_SECRET } from '../_shared/utils/constants.ts';
|
||||
} from "../_shared/stripe/stripe.ts";
|
||||
import { FEEDDECK_STRIPE_WEBHOOK_SIGNING_SECRET } from "../_shared/utils/constants.ts";
|
||||
|
||||
const relevantEvents = new Set([
|
||||
'checkout.session.completed',
|
||||
'customer.subscription.created',
|
||||
'customer.subscription.deleted',
|
||||
"checkout.session.completed",
|
||||
"customer.subscription.created",
|
||||
"customer.subscription.deleted",
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -24,7 +24,7 @@ const relevantEvents = new Set([
|
||||
*/
|
||||
Deno.serve(async (req) => {
|
||||
try {
|
||||
const signature = req.headers.get('Stripe-Signature');
|
||||
const signature = req.headers.get("Stripe-Signature");
|
||||
const body = await req.text();
|
||||
|
||||
const event = await stripe.webhooks.constructEventAsync(
|
||||
@@ -35,25 +35,25 @@ Deno.serve(async (req) => {
|
||||
cryptoProvider,
|
||||
);
|
||||
|
||||
log('debug', 'Received event', { 'event': event.type });
|
||||
log("debug", "Received event", { event: event.type });
|
||||
|
||||
if (!relevantEvents.has(event.type)) {
|
||||
return new Response(JSON.stringify({ received: true }));
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case 'customer.subscription.created':
|
||||
case 'customer.subscription.deleted': {
|
||||
case "customer.subscription.created":
|
||||
case "customer.subscription.deleted": {
|
||||
const subscription = event.data.object as Stripe.Subscription;
|
||||
await manageSubscriptionStatusChange(
|
||||
subscription.customer as string,
|
||||
event.type === 'customer.subscription.created',
|
||||
event.type === "customer.subscription.created",
|
||||
);
|
||||
return new Response(JSON.stringify({ received: true }));
|
||||
}
|
||||
case 'checkout.session.completed': {
|
||||
case "checkout.session.completed": {
|
||||
const checkoutSession = event.data.object as Stripe.Checkout.Session;
|
||||
if (checkoutSession.mode === 'subscription') {
|
||||
if (checkoutSession.mode === "subscription") {
|
||||
await manageSubscriptionStatusChange(
|
||||
checkoutSession.customer as string,
|
||||
true,
|
||||
@@ -62,15 +62,12 @@ Deno.serve(async (req) => {
|
||||
return new Response(JSON.stringify({ received: true }));
|
||||
}
|
||||
default:
|
||||
throw new Error('Unhandled relevant event');
|
||||
throw new Error("Unhandled relevant event");
|
||||
}
|
||||
} catch (err) {
|
||||
log('error', 'An unexpected error occured', { 'error': err.toString() });
|
||||
return new Response(
|
||||
'An unexpected error occured',
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
log("error", "An unexpected error occured", { error: err });
|
||||
return new Response("An unexpected error occured", {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user