mirror of
https://github.com/feeddeck/feeddeck.git
synced 2026-03-11 17:47:47 -05:00
[core] Add Tests for Sources (#98)
This commit adds tests for all available sources. This commit also fixes the parsing of Atom feeds for the RSS source, where the `dc:date` field must be used for the `publishedAt` field.
This commit is contained in:
@@ -7,10 +7,9 @@ import { Redis } from 'redis';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { getFavicon } from './utils/getFavicon.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
export const getGooglenewsFeed = async (
|
||||
_supabaseClient: SupabaseClient,
|
||||
@@ -63,11 +62,11 @@ export const getGooglenewsFeed = async (
|
||||
* Get the RSS for the provided `googlenews` url and parse it. If a feed
|
||||
* doesn't contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.googlenews.url, {
|
||||
const response = await utils.fetchWithTimeout(source.options.googlenews.url, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'googlenews',
|
||||
requestUrl: source.options.googlenews.url,
|
||||
responseStatus: response.status,
|
||||
@@ -222,7 +221,7 @@ const getMedia = async (
|
||||
return cachedMediaURL;
|
||||
}
|
||||
|
||||
const favicon = await getFavicon(entry.source?.url);
|
||||
const favicon = await feedutils.getFavicon(entry.source?.url);
|
||||
if (favicon && favicon.url.startsWith('https://')) {
|
||||
await redisClient.set(cacheKey, favicon.url);
|
||||
return favicon.url;
|
||||
|
||||
272
supabase/functions/_shared/feed/googlenews_test.ts
Normal file
272
supabase/functions/_shared/feed/googlenews_test.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { getGooglenewsFeed } from './googlenews.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const responseUrl = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
|
||||
<channel>
|
||||
<generator>NFE/5.0</generator>
|
||||
<title>Schlagzeilen - Aktuell - Google News</title>
|
||||
<link>https://news.google.com/topics/CAAqJggKIiBDQkFTRWdvSUwyMHZNRFZxYUdjU0FtUmxHZ0pFUlNnQVAB?hl=de &gl=DE &ceid=DE:de</link>
|
||||
<language>de</language>
|
||||
<webMaster>news-webmaster@google.com</webMaster>
|
||||
<copyright>2023 Google Inc.</copyright>
|
||||
<lastBuildDate>Wed, 06 Dec 2023 20:10:12 GMT</lastBuildDate>
|
||||
<description>Google News</description>
|
||||
<item>
|
||||
<title>GDL ruft von Donnerstagabend an zu eintägigem Streik auf - tagesschau.de</title>
|
||||
<link>https://news.google.com/rss/articles/CBMiOGh0dHBzOi8vd3d3LnRhZ2Vzc2NoYXUuZGUvd2lydHNjaGFmdC9nZGwtc3RyZWlrLTE5MC5odG1s0gEA?oc=5</link>
|
||||
<guid isPermaLink="false">CBMiOGh0dHBzOi8vd3d3LnRhZ2Vzc2NoYXUuZGUvd2lydHNjaGFmdC9nZGwtc3RyZWlrLTE5MC5odG1s0gEA</guid>
|
||||
<pubDate>Wed, 06 Dec 2023 19:20:00 GMT</pubDate>
|
||||
<description><ol ><li ><a href="https://news.google.com/rss/articles/CBMiOGh0dHBzOi8vd3d3LnRhZ2Vzc2NoYXUuZGUvd2lydHNjaGFmdC9nZGwtc3RyZWlrLTE5MC5odG1s0gEA?oc=5" target="_blank">GDL ruft von Donnerstagabend an zu eintägigem Streik auf </a >&nbsp;&nbsp;<font color="#6f6f6f">tagesschau.de </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMidWh0dHBzOi8vd3d3LmJpbGQuZGUvcG9saXRpay9pbmxhbmQvcG9saXRpay1pbmxhbmQvZGV1dHNjaGUtYmFobi1tb3JnZW4tbmFlY2hzdGVyLWxva2Z1ZWhyZXItc3RyZWlrLTg2MzQ4NjM0LmJpbGQuaHRtbNIBAA?oc=5" target="_blank">Deutsche Bahn: Morgen nächster Lokführer-Streik! | Politik </a >&nbsp;&nbsp;<font color="#6f6f6f">BILD </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMihAFodHRwczovL3d3dy5uZHIuZGUvbmFjaHJpY2h0ZW4vc2NobGVzd2lnLWhvbHN0ZWluL2t1cnpuYWNocmljaHRlbi9TY2hsZXN3aWctSG9sc3RlaW4tYWt0dWVsbC1OYWNocmljaHRlbi1pbS1VZWJlcmJsaWNrLG5ld3MzNTU2Lmh0bWzSAQA?oc=5" target="_blank">Schleswig-Holstein aktuell: Nachrichten im Überblick </a >&nbsp;&nbsp;<font color="#6f6f6f">NDR.de </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMiiAFodHRwczovL3d3dy5sci1vbmxpbmUuZGUvbGF1c2l0ei9jb3R0YnVzL3JlMi1jb3R0YnVzLWJlcmxpbi1zdHJlY2tlLXdpcmQtd2llZGVyLWdlc3BlcnJ0LV8td2FzLWZhaHJnYWVzdGUtYmVhY2h0ZW4tbXVlc3Nlbi03MjQ0MjYwOS5odG1s0gEA?oc=5" target="_blank">Bahnstreik und Sperrung - Nichts geht mehr beim RE2 Cottbus – Berlin </a >&nbsp;&nbsp;<font color="#6f6f6f">Lausitzer Rundschau </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMingFodHRwczovL3d3dy5zcGllZ2VsLmRlL3dpcnRzY2hhZnQvc2VydmljZS9kZXV0c2NoZS1iYWhuLWdkbC1sb2tmdWVocmVyLWRlci1iYWhuLXN0cmVpa2VuLWFiLWRvbm5lcnN0YWctYmlzLWZyZWl0YWdhYmVuZC1hLTJhN2Q1OWUyLTBmOTUtNDVhYi04OTFkLTI4NjcxMGI1MWI1MdIBAA?oc=5" target="_blank">Deutsche Bahn/GDL: Lokführer der Bahn streiken ab Donnerstag bis Freitagabend </a >&nbsp;&nbsp;<font color="#6f6f6f">DER SPIEGEL </font ></li ></ol ></description>
|
||||
<source url="https://www.tagesschau.de">tagesschau.de</source>
|
||||
</item>
|
||||
<item>
|
||||
<title>Lassen die USA die Ukraine im Stich?: US-Finanzministerin spricht von „katastrophaler Situation“ - Tagesspiegel</title>
|
||||
<link>https://news.google.com/rss/articles/CBMilgFodHRwczovL3d3dy50YWdlc3NwaWVnZWwuZGUvaW50ZXJuYXRpb25hbGVzL2xhc3Nlbi1kaWUtdXNhLWRpZS11a3JhaW5lLWltLXN0aWNoLXVzLWZpbmFuem1pbmlzdGVyaW4tc3ByaWNodC12b24ta2F0YXN0cm9waGFsZXItc2l0dWF0aW9uLTEwODg3Nzg3Lmh0bWzSAQA?oc=5</link>
|
||||
<guid isPermaLink="false">CBMilgFodHRwczovL3d3dy50YWdlc3NwaWVnZWwuZGUvaW50ZXJuYXRpb25hbGVzL2xhc3Nlbi1kaWUtdXNhLWRpZS11a3JhaW5lLWltLXN0aWNoLXVzLWZpbmFuem1pbmlzdGVyaW4tc3ByaWNodC12b24ta2F0YXN0cm9waGFsZXItc2l0dWF0aW9uLTEwODg3Nzg3Lmh0bWzSAQA</guid>
|
||||
<pubDate>Wed, 06 Dec 2023 15:38:00 GMT</pubDate>
|
||||
<description><ol ><li ><a href="https://news.google.com/rss/articles/CBMilgFodHRwczovL3d3dy50YWdlc3NwaWVnZWwuZGUvaW50ZXJuYXRpb25hbGVzL2xhc3Nlbi1kaWUtdXNhLWRpZS11a3JhaW5lLWltLXN0aWNoLXVzLWZpbmFuem1pbmlzdGVyaW4tc3ByaWNodC12b24ta2F0YXN0cm9waGFsZXItc2l0dWF0aW9uLTEwODg3Nzg3Lmh0bWzSAQA?oc=5" target="_blank">Lassen die USA die Ukraine im Stich?: US-Finanzministerin spricht von „katastrophaler Situation“</a >&nbsp;&nbsp;<font color="#6f6f6f">Tagesspiegel </font ></li ><li ><a href="https://news.google.com/rss/articles/CCAiC0lETWpHLXRoZ3ZNmAEB?oc=5" target="_blank">Rüstungskonferenz in Washington: Ukraine-Hilfen der USA laufen aus </a >&nbsp;&nbsp;<font color="#6f6f6f">tagesschau </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMiiwFodHRwczovL3d3dy5iaWxkLmRlL3BvbGl0aWsvYXVzbGFuZC9wb2xpdGlrLWF1c2xhbmQvc2VsZW5za3lqLXNhZ3QtYXVmdHJpdHQtdm9yLXVzLXNlbmF0b3Jlbi1hYi1ldHdhcy1kYXp3aXNjaGVuZ2Vrb21tZW4tODYzMzg3OTIuYmlsZC5odG1s0gEA?oc=5" target="_blank">Selenskyj sagt Auftritt vor US-Senatoren ab: „Etwas dazwischengekommen“</a >&nbsp;&nbsp;<font color="#6f6f6f">BILD </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMic2h0dHBzOi8vd3d3LnQtb25saW5lLmRlL25hY2hyaWNodGVuL2F1c2xhbmQvdXNhL3VzLXdhaGwvaWRfMTAwMjk2MDQ4L3VzYS1oYWJlbi1rZWluLWdlbGQtbWVoci1mdWVyLWRpZS11a3JhaW5lLmh0bWzSAQA?oc=5" target="_blank">USA haben kein Geld mehr für die Ukraine </a >&nbsp;&nbsp;<font color="#6f6f6f">t-online </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMibGh0dHBzOi8vd3d3Lm1vcmdlbnBvc3QuZGUvcG9saXRpay9hcnRpY2xlMjQwNzUyNTI2L1VrcmFpbmUtS3JpZWctQW1lcmlrYS1kYXJmLW5pY2h0LXZvbi1kZXItRmFobmUtZ2VoZW4uaHRtbNIBAA?oc=5" target="_blank">Ukraine-Krieg: Putins wichtigste Unterstützer sitzen in Washington </a >&nbsp;&nbsp;<font color="#6f6f6f">Berliner Morgenpost </font ></li ></ol ></description>
|
||||
<source url="https://www.tagesspiegel.de">Tagesspiegel</source>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
const responseSearch = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
|
||||
<channel>
|
||||
<generator>NFE/5.0</generator>
|
||||
<title>"Chemnitz" - Google News</title>
|
||||
<link>https://news.google.com/search?q=Chemnitz &hl=de &gl=DE &ceid=DE:de</link>
|
||||
<language>de</language>
|
||||
<webMaster>news-webmaster@google.com</webMaster>
|
||||
<copyright>2023 Google Inc.</copyright>
|
||||
<lastBuildDate>Wed, 06 Dec 2023 20:19:34 GMT</lastBuildDate>
|
||||
<description>Google News</description>
|
||||
<item>
|
||||
<title>Chemnitz: Preisschock bei "eins energie"! Fernwärme wird 75 Prozent teurer - TAG24</title>
|
||||
<link>https://news.google.com/rss/articles/CBMibGh0dHBzOi8vd3d3LnRhZzI0LmRlL2NoZW1uaXR6L2xva2FsZXMvcHJlaXNzY2hvY2stYmVpLWVpbnMtZW5lcmdpZS1mZXJud2Flcm1lLXdpcmQtNzUtcHJvemVudC10ZXVyZXItMzAzMjA5MdIBcGh0dHBzOi8vd3d3LnRhZzI0LmRlL2FtcC9jaGVtbml0ei9sb2thbGVzL3ByZWlzc2Nob2NrLWJlaS1laW5zLWVuZXJnaWUtZmVybndhZXJtZS13aXJkLTc1LXByb3plbnQtdGV1cmVyLTMwMzIwOTE?oc=5</link>
|
||||
<guid isPermaLink="false">CBMibGh0dHBzOi8vd3d3LnRhZzI0LmRlL2NoZW1uaXR6L2xva2FsZXMvcHJlaXNzY2hvY2stYmVpLWVpbnMtZW5lcmdpZS1mZXJud2Flcm1lLXdpcmQtNzUtcHJvemVudC10ZXVyZXItMzAzMjA5MdIBcGh0dHBzOi8vd3d3LnRhZzI0LmRlL2FtcC9jaGVtbml0ei9sb2thbGVzL3ByZWlzc2Nob2NrLWJlaS1laW5zLWVuZXJnaWUtZmVybndhZXJtZS13aXJkLTc1LXByb3plbnQtdGV1cmVyLTMwMzIwOTE</guid>
|
||||
<pubDate>Wed, 06 Dec 2023 04:30:00 GMT</pubDate>
|
||||
<description><a href="https://news.google.com/rss/articles/CBMibGh0dHBzOi8vd3d3LnRhZzI0LmRlL2NoZW1uaXR6L2xva2FsZXMvcHJlaXNzY2hvY2stYmVpLWVpbnMtZW5lcmdpZS1mZXJud2Flcm1lLXdpcmQtNzUtcHJvemVudC10ZXVyZXItMzAzMjA5MdIBcGh0dHBzOi8vd3d3LnRhZzI0LmRlL2FtcC9jaGVtbml0ei9sb2thbGVzL3ByZWlzc2Nob2NrLWJlaS1laW5zLWVuZXJnaWUtZmVybndhZXJtZS13aXJkLTc1LXByb3plbnQtdGV1cmVyLTMwMzIwOTE?oc=5" target="_blank">Chemnitz: Preisschock bei "eins energie"! Fernwärme wird 75 Prozent teurer </a >&nbsp;&nbsp;<font color="#6f6f6f">TAG24 </font ></description>
|
||||
<source url="https://www.tag24.de">TAG24</source>
|
||||
</item>
|
||||
<item>
|
||||
<title>Ehemaliges Chemnitzer Straßenbahndepot wird zu Garagencampus - MDR</title>
|
||||
<link>https://news.google.com/rss/articles/CBMiN2h0dHBzOi8vd3d3Lm1kci5kZS92aWRlby9tZHItdmlkZW9zL2EvdmlkZW8tNzc5OTUyLmh0bWzSAQA?oc=5</link>
|
||||
<guid isPermaLink="false">CBMiN2h0dHBzOi8vd3d3Lm1kci5kZS92aWRlby9tZHItdmlkZW9zL2EvdmlkZW8tNzc5OTUyLmh0bWzSAQA</guid>
|
||||
<pubDate>Wed, 06 Dec 2023 19:24:28 GMT</pubDate>
|
||||
<description><a href="https://news.google.com/rss/articles/CBMiN2h0dHBzOi8vd3d3Lm1kci5kZS92aWRlby9tZHItdmlkZW9zL2EvdmlkZW8tNzc5OTUyLmh0bWzSAQA?oc=5" target="_blank">Ehemaliges Chemnitzer Straßenbahndepot wird zu Garagencampus </a >&nbsp;&nbsp;<font color="#6f6f6f">MDR </font ></description>
|
||||
<source url="https://www.mdr.de">MDR</source>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
Deno.test('getGooglenewsFeed - Url', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseUrl, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getGooglenewsFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: {
|
||||
googlenews: {
|
||||
type: 'url',
|
||||
url:
|
||||
'https://news.google.com/topics/CAAqJggKIiBDQkFTRWdvSUwyMHZNRFZxYUdjU0FtUmxHZ0pFUlNnQVAB?hl=de&gl=DE&ceid=DE%3Ade',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'googlenews-myuser-mycolumn-8c1368ef1bc9e52356bffba3ce60cd48',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'googlenews',
|
||||
'title': 'Schlagzeilen - Aktuell - Google News',
|
||||
'options': {
|
||||
'googlenews': {
|
||||
'type': 'url',
|
||||
'url':
|
||||
'https://news.google.com/rss/topics/CAAqJggKIiBDQkFTRWdvSUwyMHZNRFZxYUdjU0FtUmxHZ0pFUlNnQVAB?hl=de&gl=DE&ceid=DE%3Ade',
|
||||
},
|
||||
},
|
||||
'link':
|
||||
'https://news.google.com/topics/CAAqJggKIiBDQkFTRWdvSUwyMHZNRFZxYUdjU0FtUmxHZ0pFUlNnQVAB?hl=de &gl=DE &ceid=DE:de',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'googlenews-myuser-mycolumn-8c1368ef1bc9e52356bffba3ce60cd48-b8e1de5555c562ab270e8398ee948d16',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'googlenews-myuser-mycolumn-8c1368ef1bc9e52356bffba3ce60cd48',
|
||||
'title':
|
||||
'GDL ruft von Donnerstagabend an zu eintägigem Streik auf - tagesschau.de',
|
||||
'link':
|
||||
'https://news.google.com/rss/articles/CBMiOGh0dHBzOi8vd3d3LnRhZ2Vzc2NoYXUuZGUvd2lydHNjaGFmdC9nZGwtc3RyZWlrLTE5MC5odG1s0gEA?oc=5',
|
||||
'description':
|
||||
'<ol ><li ><a href="https://news.google.com/rss/articles/CBMiOGh0dHBzOi8vd3d3LnRhZ2Vzc2NoYXUuZGUvd2lydHNjaGFmdC9nZGwtc3RyZWlrLTE5MC5odG1s0gEA?oc=5" target="_blank">GDL ruft von Donnerstagabend an zu eintägigem Streik auf </a > <font color="#6f6f6f">tagesschau.de </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMidWh0dHBzOi8vd3d3LmJpbGQuZGUvcG9saXRpay9pbmxhbmQvcG9saXRpay1pbmxhbmQvZGV1dHNjaGUtYmFobi1tb3JnZW4tbmFlY2hzdGVyLWxva2Z1ZWhyZXItc3RyZWlrLTg2MzQ4NjM0LmJpbGQuaHRtbNIBAA?oc=5" target="_blank">Deutsche Bahn: Morgen nächster Lokführer-Streik! | Politik </a > <font color="#6f6f6f">BILD </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMihAFodHRwczovL3d3dy5uZHIuZGUvbmFjaHJpY2h0ZW4vc2NobGVzd2lnLWhvbHN0ZWluL2t1cnpuYWNocmljaHRlbi9TY2hsZXN3aWctSG9sc3RlaW4tYWt0dWVsbC1OYWNocmljaHRlbi1pbS1VZWJlcmJsaWNrLG5ld3MzNTU2Lmh0bWzSAQA?oc=5" target="_blank">Schleswig-Holstein aktuell: Nachrichten im Überblick </a > <font color="#6f6f6f">NDR.de </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMiiAFodHRwczovL3d3dy5sci1vbmxpbmUuZGUvbGF1c2l0ei9jb3R0YnVzL3JlMi1jb3R0YnVzLWJlcmxpbi1zdHJlY2tlLXdpcmQtd2llZGVyLWdlc3BlcnJ0LV8td2FzLWZhaHJnYWVzdGUtYmVhY2h0ZW4tbXVlc3Nlbi03MjQ0MjYwOS5odG1s0gEA?oc=5" target="_blank">Bahnstreik und Sperrung - Nichts geht mehr beim RE2 Cottbus – Berlin </a > <font color="#6f6f6f">Lausitzer Rundschau </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMingFodHRwczovL3d3dy5zcGllZ2VsLmRlL3dpcnRzY2hhZnQvc2VydmljZS9kZXV0c2NoZS1iYWhuLWdkbC1sb2tmdWVocmVyLWRlci1iYWhuLXN0cmVpa2VuLWFiLWRvbm5lcnN0YWctYmlzLWZyZWl0YWdhYmVuZC1hLTJhN2Q1OWUyLTBmOTUtNDVhYi04OTFkLTI4NjcxMGI1MWI1MdIBAA?oc=5" target="_blank">Deutsche Bahn/GDL: Lokführer der Bahn streiken ab Donnerstag bis Freitagabend </a > <font color="#6f6f6f">DER SPIEGEL </font ></li ></ol >',
|
||||
'author': 'tagesschau.de',
|
||||
'publishedAt': 1701890400,
|
||||
}, {
|
||||
'id':
|
||||
'googlenews-myuser-mycolumn-8c1368ef1bc9e52356bffba3ce60cd48-196ae0209c5e61dd3b746eda4f7e7eef',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'googlenews-myuser-mycolumn-8c1368ef1bc9e52356bffba3ce60cd48',
|
||||
'title':
|
||||
'Lassen die USA die Ukraine im Stich?: US-Finanzministerin spricht von „katastrophaler Situation“ - Tagesspiegel',
|
||||
'link':
|
||||
'https://news.google.com/rss/articles/CBMilgFodHRwczovL3d3dy50YWdlc3NwaWVnZWwuZGUvaW50ZXJuYXRpb25hbGVzL2xhc3Nlbi1kaWUtdXNhLWRpZS11a3JhaW5lLWltLXN0aWNoLXVzLWZpbmFuem1pbmlzdGVyaW4tc3ByaWNodC12b24ta2F0YXN0cm9waGFsZXItc2l0dWF0aW9uLTEwODg3Nzg3Lmh0bWzSAQA?oc=5',
|
||||
'description':
|
||||
'<ol ><li ><a href="https://news.google.com/rss/articles/CBMilgFodHRwczovL3d3dy50YWdlc3NwaWVnZWwuZGUvaW50ZXJuYXRpb25hbGVzL2xhc3Nlbi1kaWUtdXNhLWRpZS11a3JhaW5lLWltLXN0aWNoLXVzLWZpbmFuem1pbmlzdGVyaW4tc3ByaWNodC12b24ta2F0YXN0cm9waGFsZXItc2l0dWF0aW9uLTEwODg3Nzg3Lmh0bWzSAQA?oc=5" target="_blank">Lassen die USA die Ukraine im Stich?: US-Finanzministerin spricht von „katastrophaler Situation“</a > <font color="#6f6f6f">Tagesspiegel </font ></li ><li ><a href="https://news.google.com/rss/articles/CCAiC0lETWpHLXRoZ3ZNmAEB?oc=5" target="_blank">Rüstungskonferenz in Washington: Ukraine-Hilfen der USA laufen aus </a > <font color="#6f6f6f">tagesschau </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMiiwFodHRwczovL3d3dy5iaWxkLmRlL3BvbGl0aWsvYXVzbGFuZC9wb2xpdGlrLWF1c2xhbmQvc2VsZW5za3lqLXNhZ3QtYXVmdHJpdHQtdm9yLXVzLXNlbmF0b3Jlbi1hYi1ldHdhcy1kYXp3aXNjaGVuZ2Vrb21tZW4tODYzMzg3OTIuYmlsZC5odG1s0gEA?oc=5" target="_blank">Selenskyj sagt Auftritt vor US-Senatoren ab: „Etwas dazwischengekommen“</a > <font color="#6f6f6f">BILD </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMic2h0dHBzOi8vd3d3LnQtb25saW5lLmRlL25hY2hyaWNodGVuL2F1c2xhbmQvdXNhL3VzLXdhaGwvaWRfMTAwMjk2MDQ4L3VzYS1oYWJlbi1rZWluLWdlbGQtbWVoci1mdWVyLWRpZS11a3JhaW5lLmh0bWzSAQA?oc=5" target="_blank">USA haben kein Geld mehr für die Ukraine </a > <font color="#6f6f6f">t-online </font ></li ><li ><a href="https://news.google.com/rss/articles/CBMibGh0dHBzOi8vd3d3Lm1vcmdlbnBvc3QuZGUvcG9saXRpay9hcnRpY2xlMjQwNzUyNTI2L1VrcmFpbmUtS3JpZWctQW1lcmlrYS1kYXJmLW5pY2h0LXZvbi1kZXItRmFobmUtZ2VoZW4uaHRtbNIBAA?oc=5" target="_blank">Ukraine-Krieg: Putins wichtigste Unterstützer sitzen in Washington </a > <font color="#6f6f6f">Berliner Morgenpost </font ></li ></ol >',
|
||||
'author': 'Tagesspiegel',
|
||||
'publishedAt': 1701877080,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://news.google.com/rss/topics/CAAqJggKIiBDQkFTRWdvSUwyMHZNRFZxYUdjU0FtUmxHZ0pFUlNnQVAB?hl=de&gl=DE&ceid=DE%3Ade',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseUrl, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
|
||||
Deno.test('getGooglenewsFeed - Search', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseSearch, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getGooglenewsFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: {
|
||||
googlenews: {
|
||||
type: 'search',
|
||||
search: 'Chemnitz',
|
||||
ceid: 'DE:de',
|
||||
gl: 'DE',
|
||||
hl: 'de',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'googlenews-myuser-mycolumn-5ef472fe226393772d05c92261df68e1',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'googlenews',
|
||||
'title': '"Chemnitz" - Google News',
|
||||
'options': {
|
||||
'googlenews': {
|
||||
'type': 'search',
|
||||
'search': 'Chemnitz',
|
||||
'ceid': 'DE:de',
|
||||
'gl': 'DE',
|
||||
'hl': 'de',
|
||||
'url':
|
||||
'https://news.google.com/rss/search?q=Chemnitz&hl=de&gl=DE&ceid=DE:de',
|
||||
},
|
||||
},
|
||||
'link':
|
||||
'https://news.google.com/search?q=Chemnitz &hl=de &gl=DE &ceid=DE:de',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'googlenews-myuser-mycolumn-5ef472fe226393772d05c92261df68e1-d834d987207ffda8946e25df15adea75',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'googlenews-myuser-mycolumn-5ef472fe226393772d05c92261df68e1',
|
||||
'title':
|
||||
'Chemnitz: Preisschock bei "eins energie"! Fernwärme wird 75 Prozent teurer - TAG24',
|
||||
'link':
|
||||
'https://news.google.com/rss/articles/CBMibGh0dHBzOi8vd3d3LnRhZzI0LmRlL2NoZW1uaXR6L2xva2FsZXMvcHJlaXNzY2hvY2stYmVpLWVpbnMtZW5lcmdpZS1mZXJud2Flcm1lLXdpcmQtNzUtcHJvemVudC10ZXVyZXItMzAzMjA5MdIBcGh0dHBzOi8vd3d3LnRhZzI0LmRlL2FtcC9jaGVtbml0ei9sb2thbGVzL3ByZWlzc2Nob2NrLWJlaS1laW5zLWVuZXJnaWUtZmVybndhZXJtZS13aXJkLTc1LXByb3plbnQtdGV1cmVyLTMwMzIwOTE?oc=5',
|
||||
'description':
|
||||
'<a href="https://news.google.com/rss/articles/CBMibGh0dHBzOi8vd3d3LnRhZzI0LmRlL2NoZW1uaXR6L2xva2FsZXMvcHJlaXNzY2hvY2stYmVpLWVpbnMtZW5lcmdpZS1mZXJud2Flcm1lLXdpcmQtNzUtcHJvemVudC10ZXVyZXItMzAzMjA5MdIBcGh0dHBzOi8vd3d3LnRhZzI0LmRlL2FtcC9jaGVtbml0ei9sb2thbGVzL3ByZWlzc2Nob2NrLWJlaS1laW5zLWVuZXJnaWUtZmVybndhZXJtZS13aXJkLTc1LXByb3plbnQtdGV1cmVyLTMwMzIwOTE?oc=5" target="_blank">Chemnitz: Preisschock bei "eins energie"! Fernwärme wird 75 Prozent teurer </a > <font color="#6f6f6f">TAG24 </font >',
|
||||
'author': 'TAG24',
|
||||
'publishedAt': 1701837000,
|
||||
}, {
|
||||
'id':
|
||||
'googlenews-myuser-mycolumn-5ef472fe226393772d05c92261df68e1-f9fdb0be0cc37b302c47ad155e25ae7a',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'googlenews-myuser-mycolumn-5ef472fe226393772d05c92261df68e1',
|
||||
'title':
|
||||
'Ehemaliges Chemnitzer Straßenbahndepot wird zu Garagencampus - MDR',
|
||||
'link':
|
||||
'https://news.google.com/rss/articles/CBMiN2h0dHBzOi8vd3d3Lm1kci5kZS92aWRlby9tZHItdmlkZW9zL2EvdmlkZW8tNzc5OTUyLmh0bWzSAQA?oc=5',
|
||||
'description':
|
||||
'<a href="https://news.google.com/rss/articles/CBMiN2h0dHBzOi8vd3d3Lm1kci5kZS92aWRlby9tZHItdmlkZW9zL2EvdmlkZW8tNzc5OTUyLmh0bWzSAQA?oc=5" target="_blank">Ehemaliges Chemnitzer Straßenbahndepot wird zu Garagencampus </a > <font color="#6f6f6f">MDR </font >',
|
||||
'author': 'MDR',
|
||||
'publishedAt': 1701890668,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://news.google.com/rss/search?q=Chemnitz&hl=de&gl=DE&ceid=DE:de',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseSearch, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
@@ -8,8 +8,7 @@ import { unescape } from 'lodash';
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
/**
|
||||
* `instances` contains a list of known Lemmy instances. This list is used to
|
||||
@@ -149,11 +148,11 @@ export const getLemmyFeed = async (
|
||||
* Get the RSS for the provided `lemmy` url and parse it. If a feed doesn't
|
||||
* contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.lemmy, {
|
||||
const response = await utils.fetchWithTimeout(source.options.lemmy, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'lemmy',
|
||||
requestUrl: source.options.lemmy,
|
||||
responseStatus: response.status,
|
||||
|
||||
511
supabase/functions/_shared/feed/lemmy_test.ts
Normal file
511
supabase/functions/_shared/feed/lemmy_test.ts
Normal file
@@ -0,0 +1,511 @@
|
||||
import { assertEquals } from 'std/assert';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { getLemmyFeed, isLemmyUrl } from './lemmy.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const responseUser = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
||||
<channel>
|
||||
<title>Lemmy.World - lwCET</title>
|
||||
<link>https://lemmy.world/u/lwCET</link>
|
||||
<description />
|
||||
<item>
|
||||
<title>[Weekly Community Spotlights] c/forgottenweapons and aneurysmposting@sopuli.xyz</title>
|
||||
<link>https://lemmy.world/pictrs/image/d22b2935-f27b-49f0-81e7-c81c21088467.png</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/lwCET">lwCET</a> to <a href="https://lemmy.world/c/communityspotlight">communityspotlight</a><br>180 points | <a href="https://lemmy.world/post/9209829">19 comments</a><br><a href="https://lemmy.world/pictrs/image/d22b2935-f27b-49f0-81e7-c81c21088467.png">https://lemmy.world/pictrs/image/d22b2935-f27b-49f0-81e7-c81c21088467.png</a><h2>Hello World,</h2>
|
||||
<h2>This week’s Community Spotlights are:</h2>
|
||||
<p><strong>LW Community:</strong> <a href="https://lemmy.world/c/forgottenweapons">Forgotten Weapons</a> (!forgottenweapons@lemmy.world) - A community dedicated to discussion around historical arms, mechanically unique arms, and Ian McCollum’s Forgotten Weapons content.<br />
|
||||
<strong>Mod(s):</strong> @FireTower@lemmy.world</p>
|
||||
<hr />
|
||||
<p><strong>Fediverse Community:</strong> <a href="https://sopuli.xyz/c/aneurysmposting">Aneurysm Posting</a> (!aneurysmposting@sopuli.xyz) - For shitposting by people who can smell burnt toast.<br />
|
||||
<strong>Mod(s):</strong> @PinkyCoyote@sopuli.xyz</p>
|
||||
<hr />
|
||||
<h2>How to submit a community you would like to see spotlighted</h2>
|
||||
<p>Comment on any Weekly Spotlight post or suggest a community on our <a href="https://discord.gg/lemmyworld">Discord server</a> in the <strong>community-spotlight</strong> channel. You can also send a message to the <a href="https://lemmy.world/u/lwCET">Community Team</a> with your suggestions.</p>]]></description>
|
||||
<comments>https://lemmy.world/post/9209829</comments>
|
||||
<guid>https://lemmy.world/post/9209829</guid>
|
||||
<pubDate>Wed, 06 Dec 2023 11:32:19 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/lwCET</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>LW Holiday Logos</title>
|
||||
<link>https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/lwCET">lwCET</a> to <a href="https://lemmy.world/c/lemmyworld">lemmyworld</a><br>361 points | <a href="https://lemmy.world/post/9036399">47 comments</a><br><a href="https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png">https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png</a><p>Hello World,</p>
|
||||
<p>You may have seen the LW holiday themed logos we have used for Halloween and Thanksgiving. LW’s users represent many countries around the world and we want to celebrate holidays and other special days that are local to you, but our team is fairly small and we aren’t aware of a lot of the local customs out there. So we’re asking you what you would like to see represented in a LW themed logo. What are some holidays/special days in your area and how do you celebrate them? And not just major holidays, we would like to celebrate festivals, days of remembrance, and other special days.</p>
|
||||
<p>Please, comment below your suggestions and ideas on how we could represent them in the LW logo.</p>
|
||||
<p>EDIT: Mostly looking for events throughout the year. What’s left of 2023 is already in work. Thanks!</p>]]></description>
|
||||
<comments>https://lemmy.world/post/9036399</comments>
|
||||
<guid>https://lemmy.world/post/9036399</guid>
|
||||
<pubDate>Sat, 02 Dec 2023 05:39:05 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/lwCET</dc:creator>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
const responseCommunity = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
||||
<channel>
|
||||
<title>Lemmy.World - lemmyworld</title>
|
||||
<link>https://lemmy.world/c/lemmyworld</link>
|
||||
<description>This Community is intended for **posts about the Lemmy.world server** by the admins.
|
||||
|
||||
For support with issues at Lemmy.world, go to [the Lemmy.world Support community](https://lemmy.world/c/support).
|
||||
|
||||
## Support e-mail
|
||||
Any support requests are best sent to info@lemmy.world e-mail.
|
||||
|
||||
## Donations
|
||||
If you would like to make a donation to support the cost of running this platform, please do so at the mastodon. world donation URLs:
|
||||
|
||||
- https://opencollective.com/mastodonworld
|
||||
- https://patreon.com/mastodonworld</description>
|
||||
<item>
|
||||
<title>LW Holiday Logos</title>
|
||||
<link>https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/lwCET">lwCET</a> to <a href="https://lemmy.world/c/lemmyworld">lemmyworld</a><br>361 points | <a href="https://lemmy.world/post/9036399">47 comments</a><br><a href="https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png">https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png</a><p>Hello World,</p>
|
||||
<p>You may have seen the LW holiday themed logos we have used for Halloween and Thanksgiving. LW’s users represent many countries around the world and we want to celebrate holidays and other special days that are local to you, but our team is fairly small and we aren’t aware of a lot of the local customs out there. So we’re asking you what you would like to see represented in a LW themed logo. What are some holidays/special days in your area and how do you celebrate them? And not just major holidays, we would like to celebrate festivals, days of remembrance, and other special days.</p>
|
||||
<p>Please, comment below your suggestions and ideas on how we could represent them in the LW logo.</p>
|
||||
<p>EDIT: Mostly looking for events throughout the year. What’s left of 2023 is already in work. Thanks!</p>]]></description>
|
||||
<comments>https://lemmy.world/post/9036399</comments>
|
||||
<guid>https://lemmy.world/post/9036399</guid>
|
||||
<pubDate>Sat, 02 Dec 2023 05:39:05 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/lwCET</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>Lemmy.World Junior Cloud Engineer</title>
|
||||
<link>https://lemmy.world/post/8054956</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/lwadmin">lwadmin</a> to <a href="https://lemmy.world/c/lemmyworld">lemmyworld</a><br>501 points | <a href="https://lemmy.world/post/8054956">2 comments</a><p>Hello World!</p>
|
||||
<p>Lemmy.World is looking for new engineers to help with our growing community. Volunteers will assist our existing infrastructure team with monitoring, maintenance and automation development tasks. They will report to our <a href="https://team.lemmy.world/#-org-chart">head of infrastructure</a>.</p>
|
||||
<p>We are looking for junior admins for this role. You will learn a modern cloud infra stack, including Terraform, DataDog, CloudFlare and ma</p>
|
||||
<p>Keep in mind that while this is a volunteer gig, we would ask you to be able to commit to at least 5-10 hours a week. We also understand this is a hobby and that family and work comes first.</p>
|
||||
<p>Applicants must be okay with providing their CV, LinkedIn profile; along with sitting for a video interview.</p>
|
||||
<p>We are an international team that works from both North America EST time (-4) and Europe CEST (+2) so we would ask that candidates be flexible with their availability.</p>
|
||||
<p>To learn more and begin your application process, <a href="https://docs.google.com/forms/d/e/1FAIpQLSd0wXwY4V75_sVM1BmgFL8ObfwhT2jsUwxb9MP_TY8PyE3KfQ/viewform?pli=1">click here</a>. This is not a paid position.</p>]]></description>
|
||||
<comments>https://lemmy.world/post/8054956</comments>
|
||||
<guid>https://lemmy.world/post/8054956</guid>
|
||||
<pubDate>Fri, 10 Nov 2023 09:48:21 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/lwadmin</dc:creator>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
const responseCommunityIIC = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
||||
<channel>
|
||||
<title>Lemmy.World - idiotsincars</title>
|
||||
<link>https://lemmy.world/c/idiotsincars</link>
|
||||
<description>This ~~subreddit~~ community is devoted to the lovable idiots who do hilarious, idiot things in their idiot cars (or trucks, motorcycles, tractors, or other vehicle). We honor them with gifs, videos, images, and laughter.</description>
|
||||
<item>
|
||||
<title>9/30/23 Don't be this guy</title>
|
||||
<link>https://i.imgur.com/rGcxspg.mp4</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemm.ee/u/SuperSleuth">SuperSleuth</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>97 points | <a href="https://lemmy.world/post/6063706">4 comments</a><br><a href="https://i.imgur.com/rGcxspg.mp4">https://i.imgur.com/rGcxspg.mp4</a><p>use this <a href="https://imgur.com/a/XKwjyC3">link</a> if video doesn’t load</p>]]></description>
|
||||
<comments>https://lemmy.world/post/6063706</comments>
|
||||
<guid>https://lemmy.world/post/6063706</guid>
|
||||
<pubDate>Sun, 01 Oct 2023 00:42:07 +0000</pubDate>
|
||||
<dc:creator>https://lemm.ee/u/SuperSleuth</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>Dash Cam Owners Australia September 2023 On the Road Compilation</title>
|
||||
<link>https://youtu.be/6Xr6tsMCDzs?si=apq7rpNvByYnpXN2</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/BestTestInTheWest">BestTestInTheWest</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>22 points | <a href="https://lemmy.world/post/5679506">2 comments</a><br><a href="https://youtu.be/6Xr6tsMCDzs?si=apq7rpNvByYnpXN2">https://youtu.be/6Xr6tsMCDzs?si=apq7rpNvByYnpXN2</a>]]></description>
|
||||
<comments>https://lemmy.world/post/5679506</comments>
|
||||
<guid>https://lemmy.world/post/5679506</guid>
|
||||
<pubDate>Sun, 24 Sep 2023 22:50:29 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/BestTestInTheWest</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>I merge now!</title>
|
||||
<link>https://files.catbox.moe/7ino16.mp4</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemm.ee/u/Overstuff9499">Overstuff9499</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>13 points | <a href="https://lemmy.world/post/4072199">7 comments</a><br><a href="https://files.catbox.moe/7ino16.mp4">https://files.catbox.moe/7ino16.mp4</a><p>i guess i should read their minds.</p>]]></description>
|
||||
<comments>https://lemmy.world/post/4072199</comments>
|
||||
<guid>https://lemmy.world/post/4072199</guid>
|
||||
<pubDate>Tue, 29 Aug 2023 13:56:40 +0000</pubDate>
|
||||
<dc:creator>https://lemm.ee/u/Overstuff9499</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>Dash Cam Owners Australia August 2023 On the Road Compilation</title>
|
||||
<link>https://youtu.be/TtWnAIcU6Cs?si=RQ9VJGcLF4xR0xsx</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/BestTestInTheWest">BestTestInTheWest</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>26 points | <a href="https://lemmy.world/post/3965818">1 comments</a><br><a href="https://youtu.be/TtWnAIcU6Cs?si=RQ9VJGcLF4xR0xsx">https://youtu.be/TtWnAIcU6Cs?si=RQ9VJGcLF4xR0xsx</a><p>Dash Cam Owners Australia August 2023 On the Road Compilation</p>]]></description>
|
||||
<comments>https://lemmy.world/post/3965818</comments>
|
||||
<guid>https://lemmy.world/post/3965818</guid>
|
||||
<pubDate>Sun, 27 Aug 2023 19:53:07 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/BestTestInTheWest</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>Car playing PacMan with the double yellow line in a dangerous curve</title>
|
||||
<link>https://i.imgur.com/gHFeqCi.mp4</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/LazaroFilm">LazaroFilm</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>45 points | <a href="https://lemmy.world/post/3303420">1 comments</a><br><a href="https://i.imgur.com/gHFeqCi.mp4">https://i.imgur.com/gHFeqCi.mp4</a><p>And they honk back‽</p>]]></description>
|
||||
<comments>https://lemmy.world/post/3303420</comments>
|
||||
<guid>https://lemmy.world/post/3303420</guid>
|
||||
<pubDate>Wed, 16 Aug 2023 22:26:18 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/LazaroFilm</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>Cop car forgot how stop signs work</title>
|
||||
<link>https://i.imgur.com/hsaoKWm.mp4</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/LazaroFilm">LazaroFilm</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>49 points | <a href="https://lemmy.world/post/3303348">4 comments</a><br><a href="https://i.imgur.com/hsaoKWm.mp4">https://i.imgur.com/hsaoKWm.mp4</a>]]></description>
|
||||
<comments>https://lemmy.world/post/3303348</comments>
|
||||
<guid>https://lemmy.world/post/3303348</guid>
|
||||
<pubDate>Wed, 16 Aug 2023 22:18:38 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/LazaroFilm</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>Honey, I forgot the KeÅŸkek</title>
|
||||
<link>https://i.imgur.com/lXKg8Gn.mp4</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemm.ee/u/SuperSleuth">SuperSleuth</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>108 points | <a href="https://lemmy.world/post/2916600">5 comments</a><br><a href="https://i.imgur.com/lXKg8Gn.mp4">https://i.imgur.com/lXKg8Gn.mp4</a><p>A seven car pileup during a wedding convoy in Denizli, Turkey. Click <a href="https://imgur.com/a/XzQ2rCD">here</a> if you can’t see the video.</p>]]></description>
|
||||
<comments>https://lemmy.world/post/2916600</comments>
|
||||
<guid>https://lemmy.world/post/2916600</guid>
|
||||
<pubDate>Wed, 09 Aug 2023 13:20:04 +0000</pubDate>
|
||||
<dc:creator>https://lemm.ee/u/SuperSleuth</dc:creator>
|
||||
</item>
|
||||
<item>
|
||||
<title>BMW unexpectedly using blinkers before brake checking cargo truck [YT original in post]</title>
|
||||
<link>http://regna.nu/7u70dz.gif</link>
|
||||
<description><![CDATA[submitted by <a href="https://lemmy.world/u/Regna">Regna</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>91 points | <a href="https://lemmy.world/post/2370743">11 comments</a><br><a href="http://regna.nu/7u70dz.gif">http://regna.nu/7u70dz.gif</a><p><em>Edit: Changed link for the pic to another site</em></p>
|
||||
<p>The guy behind <a href="https://www.youtube.com/@truckdriver1982">Trucker Dashcam // Sweden</a> is one of the nicest truckers I’ve ever heard of. He mainly does dashcam compilations nowadays, and includes videos from friends and subscibers as well.</p>
|
||||
<ul>
|
||||
<li>Original video is at: <a href="https://www.youtube.com/watch?v=6HivZOJ4-K4">Trucker Dashcam // Sweden</a></li>
|
||||
<li>Here is <a href="https://www.youtube.com/watch?v=6HivZOJ4-K4&t=55s">time when the BMW turns up</a></li>
|
||||
<li>Here is a <a href="https://piped.video/channel/UCfV0wtzumYCRuvLhY5n6hug">Piped link to the channel</a></li>
|
||||
</ul>]]></description>
|
||||
<comments>https://lemmy.world/post/2370743</comments>
|
||||
<guid>https://lemmy.world/post/2370743</guid>
|
||||
<pubDate>Sun, 30 Jul 2023 10:19:11 +0000</pubDate>
|
||||
<dc:creator>https://lemmy.world/u/Regna</dc:creator>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
Deno.test('isLemmyUrl', () => {
|
||||
assertEquals(isLemmyUrl('https://lemmy.world/c/lemmyworld'), true);
|
||||
assertEquals(isLemmyUrl('https://www.google.de/'), false);
|
||||
});
|
||||
|
||||
Deno.test('getLemmyFeed - User', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseUser, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getLemmyFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{ ...mockSource, options: { lemmy: 'https://lemmy.world/u/lwCET' } },
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'lemmy-myuser-mycolumn-9b51f0368938451bfbd740fad833b7a4',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'lemmy',
|
||||
'title': 'Lemmy.World - lwCET',
|
||||
'options': { 'lemmy': 'https://lemmy.world/feeds/u/lwCET.xml?sort=New' },
|
||||
'link': 'https://lemmy.world/u/lwCET',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-9b51f0368938451bfbd740fad833b7a4-1f643a920e7a0d3766558a52ddb28f96',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-9b51f0368938451bfbd740fad833b7a4',
|
||||
'title':
|
||||
'[Weekly Community Spotlights] c/forgottenweapons and aneurysmposting@sopuli.xyz',
|
||||
'link': 'https://lemmy.world/post/9209829',
|
||||
'media':
|
||||
'https://lemmy.world/pictrs/image/d22b2935-f27b-49f0-81e7-c81c21088467.png',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/lwCET">lwCET</a> to <a href="https://lemmy.world/c/communityspotlight">communityspotlight</a><br>180 points | <a href="https://lemmy.world/post/9209829">19 comments</a><br><a href="https://lemmy.world/pictrs/image/d22b2935-f27b-49f0-81e7-c81c21088467.png">https://lemmy.world/pictrs/image/d22b2935-f27b-49f0-81e7-c81c21088467.png</a><h2>Hello World,</h2>\n<h2>This week’s Community Spotlights are:</h2>\n<p><strong>LW Community:</strong> <a href="https://lemmy.world/c/forgottenweapons">Forgotten Weapons</a> (!forgottenweapons@lemmy.world) - A community dedicated to discussion around historical arms, mechanically unique arms, and Ian McCollum’s Forgotten Weapons content.<br />\n<strong>Mod(s):</strong> @FireTower@lemmy.world</p>\n<hr />\n<p><strong>Fediverse Community:</strong> <a href="https://sopuli.xyz/c/aneurysmposting">Aneurysm Posting</a> (!aneurysmposting@sopuli.xyz) - For shitposting by people who can smell burnt toast.<br />\n<strong>Mod(s):</strong> @PinkyCoyote@sopuli.xyz</p>\n<hr />\n<h2>How to submit a community you would like to see spotlighted</h2>\n<p>Comment on any Weekly Spotlight post or suggest a community on our <a href="https://discord.gg/lemmyworld">Discord server</a> in the <strong>community-spotlight</strong> channel. You can also send a message to the <a href="https://lemmy.world/u/lwCET">Community Team</a> with your suggestions.</p>',
|
||||
'author': 'https://lemmy.world/u/lwCET',
|
||||
'publishedAt': 1701862339,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-9b51f0368938451bfbd740fad833b7a4-86c6108b553905422e6268948f22ba54',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-9b51f0368938451bfbd740fad833b7a4',
|
||||
'title': 'LW Holiday Logos',
|
||||
'link': 'https://lemmy.world/post/9036399',
|
||||
'media':
|
||||
'https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/lwCET">lwCET</a> to <a href="https://lemmy.world/c/lemmyworld">lemmyworld</a><br>361 points | <a href="https://lemmy.world/post/9036399">47 comments</a><br><a href="https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png">https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png</a><p>Hello World,</p>\n<p>You may have seen the LW holiday themed logos we have used for Halloween and Thanksgiving. LW’s users represent many countries around the world and we want to celebrate holidays and other special days that are local to you, but our team is fairly small and we aren’t aware of a lot of the local customs out there. So we’re asking you what you would like to see represented in a LW themed logo. What are some holidays/special days in your area and how do you celebrate them? And not just major holidays, we would like to celebrate festivals, days of remembrance, and other special days.</p>\n<p>Please, comment below your suggestions and ideas on how we could represent them in the LW logo.</p>\n<p>EDIT: Mostly looking for events throughout the year. What’s left of 2023 is already in work. Thanks!</p>',
|
||||
'author': 'https://lemmy.world/u/lwCET',
|
||||
'publishedAt': 1701495545,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://lemmy.world/feeds/u/lwCET.xml?sort=New',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseUser, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
|
||||
Deno.test('getLemmyFeed - Community', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseCommunity, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getLemmyFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{ ...mockSource, options: { lemmy: 'https://lemmy.world/c/lemmyworld' } },
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'lemmy-myuser-mycolumn-8024684791f06e280f6fbd7217099f42',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'lemmy',
|
||||
'title': 'Lemmy.World - lemmyworld',
|
||||
'options': {
|
||||
'lemmy': 'https://lemmy.world/feeds/c/lemmyworld.xml?sort=New',
|
||||
},
|
||||
'link': 'https://lemmy.world/c/lemmyworld',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-8024684791f06e280f6fbd7217099f42-86c6108b553905422e6268948f22ba54',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-8024684791f06e280f6fbd7217099f42',
|
||||
'title': 'LW Holiday Logos',
|
||||
'link': 'https://lemmy.world/post/9036399',
|
||||
'media':
|
||||
'https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/lwCET">lwCET</a> to <a href="https://lemmy.world/c/lemmyworld">lemmyworld</a><br>361 points | <a href="https://lemmy.world/post/9036399">47 comments</a><br><a href="https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png">https://lemmy.world/pictrs/image/f3189f30-f8c8-4c4f-b957-e3a7bfd1c784.png</a><p>Hello World,</p>\n<p>You may have seen the LW holiday themed logos we have used for Halloween and Thanksgiving. LW’s users represent many countries around the world and we want to celebrate holidays and other special days that are local to you, but our team is fairly small and we aren’t aware of a lot of the local customs out there. So we’re asking you what you would like to see represented in a LW themed logo. What are some holidays/special days in your area and how do you celebrate them? And not just major holidays, we would like to celebrate festivals, days of remembrance, and other special days.</p>\n<p>Please, comment below your suggestions and ideas on how we could represent them in the LW logo.</p>\n<p>EDIT: Mostly looking for events throughout the year. What’s left of 2023 is already in work. Thanks!</p>',
|
||||
'author': 'https://lemmy.world/u/lwCET',
|
||||
'publishedAt': 1701495545,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-8024684791f06e280f6fbd7217099f42-797a3b386c90d913c61e1c831cba6f46',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-8024684791f06e280f6fbd7217099f42',
|
||||
'title': 'Lemmy.World Junior Cloud Engineer',
|
||||
'link': 'https://lemmy.world/post/8054956',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/lwadmin">lwadmin</a> to <a href="https://lemmy.world/c/lemmyworld">lemmyworld</a><br>501 points | <a href="https://lemmy.world/post/8054956">2 comments</a><p>Hello World!</p>\n<p>Lemmy.World is looking for new engineers to help with our growing community. Volunteers will assist our existing infrastructure team with monitoring, maintenance and automation development tasks. They will report to our <a href="https://team.lemmy.world/#-org-chart">head of infrastructure</a>.</p>\n<p>We are looking for junior admins for this role. You will learn a modern cloud infra stack, including Terraform, DataDog, CloudFlare and ma</p>\n<p>Keep in mind that while this is a volunteer gig, we would ask you to be able to commit to at least 5-10 hours a week. We also understand this is a hobby and that family and work comes first.</p>\n<p>Applicants must be okay with providing their CV, LinkedIn profile; along with sitting for a video interview.</p>\n<p>We are an international team that works from both North America EST time (-4) and Europe CEST (+2) so we would ask that candidates be flexible with their availability.</p>\n<p>To learn more and begin your application process, <a href="https://docs.google.com/forms/d/e/1FAIpQLSd0wXwY4V75_sVM1BmgFL8ObfwhT2jsUwxb9MP_TY8PyE3KfQ/viewform?pli=1">click here</a>. This is not a paid position.</p>',
|
||||
'author': 'https://lemmy.world/u/lwadmin',
|
||||
'publishedAt': 1699609701,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://lemmy.world/feeds/c/lemmyworld.xml?sort=New',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseCommunity, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
|
||||
Deno.test('getLemmyFeed - Community - IIC', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseCommunityIIC, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getLemmyFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: { lemmy: 'https://lemmy.world/feeds/c/idiotsincars.xml' },
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'lemmy',
|
||||
'title': 'Lemmy.World - idiotsincars',
|
||||
'options': {
|
||||
'lemmy': 'https://lemmy.world/feeds/c/idiotsincars.xml?sort=New',
|
||||
},
|
||||
'link': 'https://lemmy.world/c/idiotsincars',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f-33d0c935222609e6d7afe3d3054affec',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'title': '9/30/23 Don\'t be this guy',
|
||||
'link': 'https://lemmy.world/post/6063706',
|
||||
'media': 'https://i.imgur.com/rGcxspg.mp4',
|
||||
'description':
|
||||
'submitted by <a href="https://lemm.ee/u/SuperSleuth">SuperSleuth</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>97 points | <a href="https://lemmy.world/post/6063706">4 comments</a><br><a href="https://i.imgur.com/rGcxspg.mp4">https://i.imgur.com/rGcxspg.mp4</a><p>use this <a href="https://imgur.com/a/XKwjyC3">link</a> if video doesn’t load</p>',
|
||||
'author': 'https://lemm.ee/u/SuperSleuth',
|
||||
'publishedAt': 1696120927,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f-44104b8b3612c665abe8b93a2cdd2ce0',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'title':
|
||||
'Dash Cam Owners Australia September 2023 On the Road Compilation',
|
||||
'link': 'https://lemmy.world/post/5679506',
|
||||
'media': 'https://youtu.be/6Xr6tsMCDzs?si=apq7rpNvByYnpXN2',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/BestTestInTheWest">BestTestInTheWest</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>22 points | <a href="https://lemmy.world/post/5679506">2 comments</a><br><a href="https://youtu.be/6Xr6tsMCDzs?si=apq7rpNvByYnpXN2">https://youtu.be/6Xr6tsMCDzs?si=apq7rpNvByYnpXN2</a>',
|
||||
'author': 'https://lemmy.world/u/BestTestInTheWest',
|
||||
'publishedAt': 1695595829,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f-d36d08de3466c62c4feb90c491e68113',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'title': 'I merge now!',
|
||||
'link': 'https://lemmy.world/post/4072199',
|
||||
'media': 'https://files.catbox.moe/7ino16.mp4',
|
||||
'description':
|
||||
'submitted by <a href="https://lemm.ee/u/Overstuff9499">Overstuff9499</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>13 points | <a href="https://lemmy.world/post/4072199">7 comments</a><br><a href="https://files.catbox.moe/7ino16.mp4">https://files.catbox.moe/7ino16.mp4</a><p>i guess i should read their minds.</p>',
|
||||
'author': 'https://lemm.ee/u/Overstuff9499',
|
||||
'publishedAt': 1693317400,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f-325c767708c578205330addd91232fc8',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'title': 'Dash Cam Owners Australia August 2023 On the Road Compilation',
|
||||
'link': 'https://lemmy.world/post/3965818',
|
||||
'media': 'https://youtu.be/TtWnAIcU6Cs?si=RQ9VJGcLF4xR0xsx',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/BestTestInTheWest">BestTestInTheWest</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>26 points | <a href="https://lemmy.world/post/3965818">1 comments</a><br><a href="https://youtu.be/TtWnAIcU6Cs?si=RQ9VJGcLF4xR0xsx">https://youtu.be/TtWnAIcU6Cs?si=RQ9VJGcLF4xR0xsx</a><p>Dash Cam Owners Australia August 2023 On the Road Compilation</p>',
|
||||
'author': 'https://lemmy.world/u/BestTestInTheWest',
|
||||
'publishedAt': 1693165987,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f-6cea754fc3ac4867b66309151e8ef5eb',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'title':
|
||||
'Car playing PacMan with the double yellow line in a dangerous curve',
|
||||
'link': 'https://lemmy.world/post/3303420',
|
||||
'media': 'https://i.imgur.com/gHFeqCi.mp4',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/LazaroFilm">LazaroFilm</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>45 points | <a href="https://lemmy.world/post/3303420">1 comments</a><br><a href="https://i.imgur.com/gHFeqCi.mp4">https://i.imgur.com/gHFeqCi.mp4</a><p>And they honk back‽</p>',
|
||||
'author': 'https://lemmy.world/u/LazaroFilm',
|
||||
'publishedAt': 1692224778,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f-08bb1f161a61a21a47457171e25c3fd1',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'title': 'Cop car forgot how stop signs work',
|
||||
'link': 'https://lemmy.world/post/3303348',
|
||||
'media': 'https://i.imgur.com/hsaoKWm.mp4',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/LazaroFilm">LazaroFilm</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>49 points | <a href="https://lemmy.world/post/3303348">4 comments</a><br><a href="https://i.imgur.com/hsaoKWm.mp4">https://i.imgur.com/hsaoKWm.mp4</a>',
|
||||
'author': 'https://lemmy.world/u/LazaroFilm',
|
||||
'publishedAt': 1692224318,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f-a567cf7498d0506a2f8a954354aa95c0',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'title': 'Honey, I forgot the KeÅŸkek',
|
||||
'link': 'https://lemmy.world/post/2916600',
|
||||
'media': 'https://i.imgur.com/lXKg8Gn.mp4',
|
||||
'description':
|
||||
'submitted by <a href="https://lemm.ee/u/SuperSleuth">SuperSleuth</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>108 points | <a href="https://lemmy.world/post/2916600">5 comments</a><br><a href="https://i.imgur.com/lXKg8Gn.mp4">https://i.imgur.com/lXKg8Gn.mp4</a><p>A seven car pileup during a wedding convoy in Denizli, Turkey. Click <a href="https://imgur.com/a/XzQ2rCD">here</a> if you can’t see the video.</p>',
|
||||
'author': 'https://lemm.ee/u/SuperSleuth',
|
||||
'publishedAt': 1691587204,
|
||||
}, {
|
||||
'id':
|
||||
'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f-ad7fbbbe7e47d9ca72aded5c66ab5bde',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'lemmy-myuser-mycolumn-e5cb4d4594ce7d19987fd42ca1dd837f',
|
||||
'title':
|
||||
'BMW unexpectedly using blinkers before brake checking cargo truck [YT original in post]',
|
||||
'link': 'https://lemmy.world/post/2370743',
|
||||
'media': 'http://regna.nu/7u70dz.gif',
|
||||
'description':
|
||||
'submitted by <a href="https://lemmy.world/u/Regna">Regna</a> to <a href="https://lemmy.world/c/idiotsincars">idiotsincars</a><br>91 points | <a href="https://lemmy.world/post/2370743">11 comments</a><br><a href="http://regna.nu/7u70dz.gif">http://regna.nu/7u70dz.gif</a><p><em>Edit: Changed link for the pic to another site</em></p>\n<p>The guy behind <a href="https://www.youtube.com/@truckdriver1982">Trucker Dashcam // Sweden</a> is one of the nicest truckers I’ve ever heard of. He mainly does dashcam compilations nowadays, and includes videos from friends and subscibers as well.</p>\n<ul>\n<li>Original video is at: <a href="https://www.youtube.com/watch?v=6HivZOJ4-K4">Trucker Dashcam // Sweden</a></li>\n<li>Here is <a href="https://www.youtube.com/watch?v=6HivZOJ4-K4&t=55s">time when the BMW turns up</a></li>\n<li>Here is a <a href="https://piped.video/channel/UCfV0wtzumYCRuvLhY5n6hug">Piped link to the channel</a></li>\n</ul>',
|
||||
'author': 'https://lemmy.world/u/Regna',
|
||||
'publishedAt': 1690712351,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://lemmy.world/feeds/c/idiotsincars.xml?sort=New',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseCommunityIIC, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
@@ -7,10 +7,9 @@ import { unescape } from 'lodash';
|
||||
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { uploadSourceIcon } from './utils/uploadFile.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
export const getMastodonFeed = async (
|
||||
supabaseClient: SupabaseClient,
|
||||
@@ -43,13 +42,13 @@ export const getMastodonFeed = async (
|
||||
/**
|
||||
* Get the RSS for the provided Mastodon username, hashtag or url.
|
||||
*/
|
||||
const response = await fetchWithTimeout(
|
||||
const response = await utils.fetchWithTimeout(
|
||||
source.options.mastodon,
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'mastodon',
|
||||
requestUrl: source.options.mastodon,
|
||||
responseStatus: response.status,
|
||||
@@ -88,7 +87,7 @@ export const getMastodonFeed = async (
|
||||
*/
|
||||
if (!source.icon && feed.image?.url) {
|
||||
source.icon = feed.image.url;
|
||||
source.icon = await uploadSourceIcon(supabaseClient, source);
|
||||
source.icon = await feedutils.uploadSourceIcon(supabaseClient, source);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
316
supabase/functions/_shared/feed/mastodon_test.ts
Normal file
316
supabase/functions/_shared/feed/mastodon_test.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { getMastodonFeed } from './mastodon.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const responseTag = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:media="http://search.yahoo.com/mrss/" xmlns:webfeeds="http://webfeeds.org/rss/1.0" version="2.0">
|
||||
<channel>
|
||||
<title>#kubernetes</title>
|
||||
<description>Public posts tagged #kubernetes</description>
|
||||
<link>https://hachyderm.io/tags/kubernetes</link>
|
||||
<lastBuildDate>Fri, 08 Dec 2023 12:52:54 +0000</lastBuildDate>
|
||||
<generator>Mastodon v4.2.1</generator>
|
||||
<item>
|
||||
<guid isPermaLink="true">https://ioc.exchange/@jon404/111544891736153138</guid>
|
||||
<link>https://ioc.exchange/@jon404/111544891736153138</link>
|
||||
<pubDate>Fri, 08 Dec 2023 12:52:54 +0000</pubDate>
|
||||
<description><p>Shameless blog series plug</p><hr><p>If you're in to <a href="https://ioc.exchange/tags/kubernetes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>kubernetes</span></a> and <a href="https://ioc.exchange/tags/homelab" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>homelab</span></a> setups, I've started a blog series at LQ.org that will cover the <a href="https://ioc.exchange/tags/virtualization" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>virtualization</span></a>, network integration, and <a href="https://ioc.exchange/tags/selfhosted" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>selfhosted</span></a> apps I'll be running in my on-prem mini k8s cluster. </p><p>I'm planning on running Alpine/Xen on refurbished desktop machines, with Debian VMs/k8s-1.28 and MetalLB/Calico for integrating into my <a href="https://ioc.exchange/tags/OpenBSD" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>OpenBSD</span></a> / <a href="https://ioc.exchange/tags/BGP" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>BGP</span></a> / <a href="https://ioc.exchange/tags/OSPF" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>OSPF</span></a> network backbone.</p><p><a href="https://www.linuxquestions.org/questions/blog/rocket357-328529/on-premise-kubernetes-part-1-5-39090/" rel="nofollow noopener noreferrer" translate="no" target="_blank"><span class="invisible">https://www.</span><span class="ellipsis">linuxquestions.org/questions/b</span><span class="invisible">log/rocket357-328529/on-premise-kubernetes-part-1-5-39090/</span></a></p></description>
|
||||
<category>kubernetes</category>
|
||||
<category>homelab</category>
|
||||
<category>selfhosted</category>
|
||||
<category>openbsd</category>
|
||||
<category>bgp</category>
|
||||
<category>ospf</category>
|
||||
<category>virtualization</category>
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="true">https://botsin.space/@k8s_releases/111544452566443481</guid>
|
||||
<link>https://botsin.space/@k8s_releases/111544452566443481</link>
|
||||
<pubDate>Fri, 08 Dec 2023 11:01:13 +0000</pubDate>
|
||||
<description><p>New Kubernetes Release Candidate Release</p><p>✨ Kubernetes v1.29.0-rc.2 ✨</p><p><a href="https://github.com/kubernetes/kubernetes/releases/tag/v1.29.0-rc.2" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://</span><span class="ellipsis">github.com/kubernetes/kubernet</span><span class="invisible">es/releases/tag/v1.29.0-rc.2</span></a></p><p><a href="https://botsin.space/tags/Kubernetes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Kubernetes</span></a> <a href="https://botsin.space/tags/k8s" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>k8s</span></a> <a href="https://botsin.space/tags/kube" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>kube</span></a></p></description>
|
||||
<category>kubernetes</category>
|
||||
<category>k8s</category>
|
||||
<category>kube</category>
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="true">https://vmst.io/@ErikBussink/111544298911638567</guid>
|
||||
<link>https://vmst.io/@ErikBussink/111544298911638567</link>
|
||||
<pubDate>Fri, 08 Dec 2023 10:22:08 +0000</pubDate>
|
||||
<description><p>Plan for the weekend<br>- Deploy the add-on TPM2 keys on 4 supermicro servers and enable the encryption service on my <a href="https://vmst.io/tags/vSphere" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>vSphere</span></a> cluster<br>- Attach some Tigo solar optimizer on some solar panels<br>- deploy a new set of NSX ALB load-balancer VM for integration in <a href="https://vmst.io/tags/NSX" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>NSX</span></a> for <a href="https://vmst.io/tags/kubernetes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>kubernetes</span></a> <br><a href="https://vmst.io/tags/HomeDC" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>HomeDC</span></a> <a href="https://vmst.io/tags/homelab" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>homelab</span></a></p></description>
|
||||
<category>vSphere</category>
|
||||
<category>nsx</category>
|
||||
<category>kubernetes</category>
|
||||
<category>homedc</category>
|
||||
<category>homelab</category>
|
||||
</item>
|
||||
<item>
|
||||
<guid isPermaLink="true">https://fosstodon.org/@imdciangot/111544228725688234</guid>
|
||||
<link>https://fosstodon.org/@imdciangot/111544228725688234</link>
|
||||
<pubDate>Fri, 08 Dec 2023 10:04:17 +0000</pubDate>
|
||||
<description><p>🚀 ALERT: Seamlessly Access HPC Resources, possible addictive 🚀</p><p>The Interlink project aims to bridge the gap between Kubernetes and HPC environments, enabling seamless access to world-class HPC centers, all within the familiar context of cloud-based computing interfaces.</p><p>Learn how with this first demo, and join the SciGeeks crew for more!</p><p><a href="https://youtu.be/-djIQGPvYdI?si=wBPHbz3iu7A3qOSx" rel="nofollow noopener noreferrer" translate="no" target="_blank"><span class="invisible">https://</span><span class="ellipsis">youtu.be/-djIQGPvYdI?si=wBPHbz</span><span class="invisible">3iu7A3qOSx</span></a></p><p>Super early stage of this adventure, give feedback!</p><p><a href="https://fosstodon.org/tags/kubernetes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>kubernetes</span></a> <a href="https://fosstodon.org/tags/HPC" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>HPC</span></a> <a href="https://fosstodon.org/tags/DistributedComputing" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>DistributedComputing</span></a> <a href="https://fosstodon.org/tags/Research" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Research</span></a> <a href="https://fosstodon.org/tags/ai" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>ai</span></a> <a href="https://fosstodon.org/tags/digitaltwins" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>digitaltwins</span></a></p></description>
|
||||
<media:content url="https://media.hachyderm.io/cache/media_attachments/files/111/544/228/748/711/741/original/84994e14c0b7384e.png" type="image/png" fileSize="965382" medium="image">
|
||||
<media:rating scheme="urn:simple">nonadult</media:rating>
|
||||
</media:content>
|
||||
<category>kubernetes</category>
|
||||
<category>hpc</category>
|
||||
<category>distributedcomputing</category>
|
||||
<category>research</category>
|
||||
<category>ai</category>
|
||||
<category>digitaltwins</category>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
const responseUser = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:media="http://search.yahoo.com/mrss/" xmlns:webfeeds="http://webfeeds.org/rss/1.0" version="2.0">
|
||||
<channel>
|
||||
<title>Rico Berger</title>
|
||||
<description>Public posts from @ricoberger@hachyderm.io</description>
|
||||
<link>https://hachyderm.io/@ricoberger</link>
|
||||
<image>
|
||||
<url>https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png</url>
|
||||
<title>Rico Berger</title>
|
||||
<link>https://hachyderm.io/@ricoberger</link>
|
||||
</image>
|
||||
<lastBuildDate>Sun, 29 Jan 2023 17:56:17 +0000</lastBuildDate>
|
||||
<webfeeds:icon>https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png</webfeeds:icon>
|
||||
<generator>Mastodon v4.2.1</generator>
|
||||
<item>
|
||||
<guid isPermaLink="true">https://hachyderm.io/@ricoberger/109773781555026547</guid>
|
||||
<link>https://hachyderm.io/@ricoberger/109773781555026547</link>
|
||||
<pubDate>Sun, 29 Jan 2023 17:56:17 +0000</pubDate>
|
||||
<description><p>🎉🎉🎉 kubenav v4 the <a href="https://hachyderm.io/tags/kubernetes" class="mention hashtag" rel="tag">#<span>kubernetes</span></a> dashboard for iOS and Android is now available <a href="https://kubenav.io" target="_blank" rel="nofollow noopener noreferrer" translate="no"><span class="invisible">https://</span><span class="">kubenav.io</span><span class="invisible"></span></a> 🥳🥳🥳</p></description>
|
||||
<media:content url="https://media.hachyderm.io/media_attachments/files/109/773/779/869/674/363/original/ac40bbbaa3710aa1.png" type="image/png" fileSize="204505" medium="image">
|
||||
<media:rating scheme="urn:simple">nonadult</media:rating>
|
||||
</media:content>
|
||||
<media:content url="https://media.hachyderm.io/media_attachments/files/109/773/779/875/654/377/original/c0147e5b7f00c319.png" type="image/png" fileSize="183170" medium="image">
|
||||
<media:rating scheme="urn:simple">nonadult</media:rating>
|
||||
</media:content>
|
||||
<media:content url="https://media.hachyderm.io/media_attachments/files/109/773/779/881/805/268/original/352c2b12ba3611ef.png" type="image/png" fileSize="289689" medium="image">
|
||||
<media:rating scheme="urn:simple">nonadult</media:rating>
|
||||
</media:content>
|
||||
<media:content url="https://media.hachyderm.io/media_attachments/files/109/773/779/882/749/096/original/ce2283bdeb25b07f.png" type="image/png" fileSize="290752" medium="image">
|
||||
<media:rating scheme="urn:simple">nonadult</media:rating>
|
||||
</media:content>
|
||||
<category>kubernetes</category>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
Deno.test('getMastodonFeed - Tag', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseTag, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getMastodonFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: { mastodon: 'https://hachyderm.io/tags/Kubernetes' },
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'mastodon',
|
||||
'title': '#kubernetes',
|
||||
'options': { 'mastodon': 'https://hachyderm.io/tags/Kubernetes.rss' },
|
||||
'link': 'https://hachyderm.io/tags/kubernetes',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027-5abb9cbadfb77c35a46b5c21fa96622e',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027',
|
||||
'title': '',
|
||||
'link': 'https://ioc.exchange/@jon404/111544891736153138',
|
||||
'description':
|
||||
'<p>Shameless blog series plug</p><hr><p>If you\'re in to <a href="https://ioc.exchange/tags/kubernetes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>kubernetes</span></a> and <a href="https://ioc.exchange/tags/homelab" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>homelab</span></a> setups, I\'ve started a blog series at LQ.org that will cover the <a href="https://ioc.exchange/tags/virtualization" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>virtualization</span></a>, network integration, and <a href="https://ioc.exchange/tags/selfhosted" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>selfhosted</span></a> apps I\'ll be running in my on-prem mini k8s cluster. </p><p>I\'m planning on running Alpine/Xen on refurbished desktop machines, with Debian VMs/k8s-1.28 and MetalLB/Calico for integrating into my <a href="https://ioc.exchange/tags/OpenBSD" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>OpenBSD</span></a> / <a href="https://ioc.exchange/tags/BGP" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>BGP</span></a> / <a href="https://ioc.exchange/tags/OSPF" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>OSPF</span></a> network backbone.</p><p><a href="https://www.linuxquestions.org/questions/blog/rocket357-328529/on-premise-kubernetes-part-1-5-39090/" rel="nofollow noopener noreferrer" translate="no" target="_blank"><span class="invisible">https://www.</span><span class="ellipsis">linuxquestions.org/questions/b</span><span class="invisible">log/rocket357-328529/on-premise-kubernetes-part-1-5-39090/</span></a></p>',
|
||||
'author': '@jon404@ioc.exchange',
|
||||
'publishedAt': 1702039974,
|
||||
}, {
|
||||
'id':
|
||||
'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027-0ef142642e2dfd8cf186543d81e2b89c',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027',
|
||||
'title': '',
|
||||
'link': 'https://botsin.space/@k8s_releases/111544452566443481',
|
||||
'description':
|
||||
'<p>New Kubernetes Release Candidate Release</p><p>✨ Kubernetes v1.29.0-rc.2 ✨</p><p><a href="https://github.com/kubernetes/kubernetes/releases/tag/v1.29.0-rc.2" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://</span><span class="ellipsis">github.com/kubernetes/kubernet</span><span class="invisible">es/releases/tag/v1.29.0-rc.2</span></a></p><p><a href="https://botsin.space/tags/Kubernetes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Kubernetes</span></a> <a href="https://botsin.space/tags/k8s" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>k8s</span></a> <a href="https://botsin.space/tags/kube" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>kube</span></a></p>',
|
||||
'author': '@k8s_releases@botsin.space',
|
||||
'publishedAt': 1702033273,
|
||||
}, {
|
||||
'id':
|
||||
'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027-ab07d25df9ede52a07a36c203261bef4',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027',
|
||||
'title': '',
|
||||
'link': 'https://vmst.io/@ErikBussink/111544298911638567',
|
||||
'description':
|
||||
'<p>Plan for the weekend<br>- Deploy the add-on TPM2 keys on 4 supermicro servers and enable the encryption service on my <a href="https://vmst.io/tags/vSphere" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>vSphere</span></a> cluster<br>- Attach some Tigo solar optimizer on some solar panels<br>- deploy a new set of NSX ALB load-balancer VM for integration in <a href="https://vmst.io/tags/NSX" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>NSX</span></a> for <a href="https://vmst.io/tags/kubernetes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>kubernetes</span></a> <br><a href="https://vmst.io/tags/HomeDC" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>HomeDC</span></a> <a href="https://vmst.io/tags/homelab" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>homelab</span></a></p>',
|
||||
'author': '@ErikBussink@vmst.io',
|
||||
'publishedAt': 1702030928,
|
||||
}, {
|
||||
'id':
|
||||
'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027-5c1055543a76a190ae210057583b2225',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'mastodon-myuser-mycolumn-91151e10882e4fff432ff509b8c6b027',
|
||||
'title': '',
|
||||
'link': 'https://fosstodon.org/@imdciangot/111544228725688234',
|
||||
'options': {
|
||||
'media': [
|
||||
'https://media.hachyderm.io/cache/media_attachments/files/111/544/228/748/711/741/original/84994e14c0b7384e.png',
|
||||
],
|
||||
},
|
||||
'description':
|
||||
'<p>🚀 ALERT: Seamlessly Access HPC Resources, possible addictive 🚀</p><p>The Interlink project aims to bridge the gap between Kubernetes and HPC environments, enabling seamless access to world-class HPC centers, all within the familiar context of cloud-based computing interfaces.</p><p>Learn how with this first demo, and join the SciGeeks crew for more!</p><p><a href="https://youtu.be/-djIQGPvYdI?si=wBPHbz3iu7A3qOSx" rel="nofollow noopener noreferrer" translate="no" target="_blank"><span class="invisible">https://</span><span class="ellipsis">youtu.be/-djIQGPvYdI?si=wBPHbz</span><span class="invisible">3iu7A3qOSx</span></a></p><p>Super early stage of this adventure, give feedback!</p><p><a href="https://fosstodon.org/tags/kubernetes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>kubernetes</span></a> <a href="https://fosstodon.org/tags/HPC" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>HPC</span></a> <a href="https://fosstodon.org/tags/DistributedComputing" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>DistributedComputing</span></a> <a href="https://fosstodon.org/tags/Research" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Research</span></a> <a href="https://fosstodon.org/tags/ai" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>ai</span></a> <a href="https://fosstodon.org/tags/digitaltwins" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>digitaltwins</span></a></p>',
|
||||
'author': '@imdciangot@fosstodon.org',
|
||||
'publishedAt': 1702029857,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: ['https://hachyderm.io/tags/Kubernetes.rss', { method: 'get' }, 5000],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseTag, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
|
||||
Deno.test('getMastodonFeed - User', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseUser, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const uploadSourceIconSpy = stub(
|
||||
feedutils,
|
||||
'uploadSourceIcon',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(
|
||||
'https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png',
|
||||
);
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getMastodonFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{ ...mockSource, options: { mastodon: '@ricoberger@hachyderm.io' } },
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'mastodon-myuser-mycolumn-5673bdae9d06a0744e93d647fe5cef2e',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'mastodon',
|
||||
'title': 'Rico Berger',
|
||||
'options': { 'mastodon': 'https://hachyderm.io/@ricoberger.rss' },
|
||||
'link': 'https://hachyderm.io/@ricoberger',
|
||||
'icon':
|
||||
'https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'mastodon-myuser-mycolumn-5673bdae9d06a0744e93d647fe5cef2e-576a014e46fbb2feae01d3143d1ec565',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'mastodon-myuser-mycolumn-5673bdae9d06a0744e93d647fe5cef2e',
|
||||
'title': '',
|
||||
'link': 'https://hachyderm.io/@ricoberger/109773781555026547',
|
||||
'options': {
|
||||
'media': [
|
||||
'https://media.hachyderm.io/media_attachments/files/109/773/779/869/674/363/original/ac40bbbaa3710aa1.png',
|
||||
'https://media.hachyderm.io/media_attachments/files/109/773/779/875/654/377/original/c0147e5b7f00c319.png',
|
||||
'https://media.hachyderm.io/media_attachments/files/109/773/779/881/805/268/original/352c2b12ba3611ef.png',
|
||||
'https://media.hachyderm.io/media_attachments/files/109/773/779/882/749/096/original/ce2283bdeb25b07f.png',
|
||||
],
|
||||
},
|
||||
'description':
|
||||
'<p>🎉🎉🎉 kubenav v4 the <a href="https://hachyderm.io/tags/kubernetes" class="mention hashtag" rel="tag">#<span>kubernetes</span></a> dashboard for iOS and Android is now available <a href="https://kubenav.io" target="_blank" rel="nofollow noopener noreferrer" translate="no"><span class="invisible">https://</span><span class="">kubenav.io</span><span class="invisible"></span></a> 🥳🥳🥳</p>',
|
||||
'author': '@ricoberger@hachyderm.io',
|
||||
'publishedAt': 1675014977,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
uploadSourceIconSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: ['https://hachyderm.io/@ricoberger.rss', { method: 'get' }, 5000],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseUser, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCall(uploadSourceIconSpy, 0, {
|
||||
args: [
|
||||
supabaseClient,
|
||||
{
|
||||
'id': 'mastodon-myuser-mycolumn-5673bdae9d06a0744e93d647fe5cef2e',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'mastodon',
|
||||
'title': 'Rico Berger',
|
||||
'options': { 'mastodon': 'https://hachyderm.io/@ricoberger.rss' },
|
||||
'link': 'https://hachyderm.io/@ricoberger',
|
||||
'icon':
|
||||
'https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png',
|
||||
},
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(undefined);
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
assertSpyCalls(uploadSourceIconSpy, 1);
|
||||
});
|
||||
@@ -7,11 +7,54 @@ import { unescape } from 'lodash';
|
||||
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { Favicon, getFavicon } from './utils/getFavicon.ts';
|
||||
import { uploadSourceIcon } from './utils/uploadFile.ts';
|
||||
import { Favicon, feedutils } from './utils/index.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
/**
|
||||
* `faviconFilter` is a filter function for the favicons. It filters out all the
|
||||
* favicons which are not hosted on the Medium CDN.
|
||||
*/
|
||||
export const faviconFilter = (favicons: Favicon[]): Favicon[] => {
|
||||
return favicons.filter((favicon) => {
|
||||
return favicon.url.startsWith('https://cdn-images');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* `parseMediumOption` parses the provided `medium` option and returns a valid
|
||||
* Medium feed url. The `medium` option can be a Medium url, a Medium tag or a
|
||||
* Medium username. If the provided option is not valid we throw an error.
|
||||
*/
|
||||
export const parseMediumOption = (input?: string): string => {
|
||||
if (input) {
|
||||
if (input.length > 1 && input[0] === '#') {
|
||||
return `https://medium.com/feed/tag/${input.slice(1)}`;
|
||||
} else if (input.length > 1 && input[0] === '@') {
|
||||
return `https://medium.com/feed/${input}`;
|
||||
} else {
|
||||
const parsedUrl = new URL(input);
|
||||
const parsedHostname = parsedUrl.hostname.split('.');
|
||||
if (
|
||||
parsedHostname.length === 2 && parsedHostname[0] === 'medium' &&
|
||||
parsedHostname[1] === 'com'
|
||||
) {
|
||||
return `https://medium.com/feed/${
|
||||
input.replace('https://medium.com/', '').replace('feed/', '')
|
||||
}`;
|
||||
} else if (
|
||||
parsedHostname.length === 3 && parsedHostname[1] === 'medium' &&
|
||||
parsedHostname[2] === 'com'
|
||||
) {
|
||||
return `https://${parsedHostname[0]}.medium.com/feed`;
|
||||
} else {
|
||||
throw new Error('Invalid source options');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid source options');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* `isMediumUrl` checks if the provided `url` is a valid Medium url. A url is
|
||||
@@ -28,51 +71,19 @@ export const getMediumFeed = async (
|
||||
_profile: IProfile,
|
||||
source: ISource,
|
||||
): Promise<{ source: ISource; items: IItem[] }> => {
|
||||
/**
|
||||
* Since the `medium` option supports multiple input format we need to
|
||||
* normalize it to a valid Medium feed url. If this is not possible we
|
||||
* consider the provided option as invalid.
|
||||
*/
|
||||
if (source.options?.medium) {
|
||||
const input = source.options.medium;
|
||||
if (input.length > 1 && input[0] === '#') {
|
||||
source.options.medium = `https://medium.com/feed/tag/${input.slice(1)}`;
|
||||
} else if (input.length > 1 && input[0] === '@') {
|
||||
source.options.medium = `https://medium.com/feed/${input}`;
|
||||
} else {
|
||||
const parsedUrl = new URL(input);
|
||||
const parsedHostname = parsedUrl.hostname.split('.');
|
||||
if (
|
||||
parsedHostname.length === 2 && parsedHostname[0] === 'medium' &&
|
||||
parsedHostname[1] === 'com'
|
||||
) {
|
||||
source.options.medium = `https://medium.com/feed/${
|
||||
input.replace('https://medium.com/', '').replace('feed/', '')
|
||||
}`;
|
||||
} else if (
|
||||
parsedHostname.length === 3 && parsedHostname[1] === 'medium' &&
|
||||
parsedHostname[2] === 'com'
|
||||
) {
|
||||
source.options.medium = `https://${parsedHostname[0]}.medium.com/feed`;
|
||||
} else {
|
||||
throw new Error('Invalid source options');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid source options');
|
||||
}
|
||||
const parsedMediumOption = parseMediumOption(source.options?.medium);
|
||||
|
||||
/**
|
||||
* Get the RSS for the provided `medium` url and parse it. If a feed doesn't
|
||||
* contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.medium, {
|
||||
const response = await utils.fetchWithTimeout(parsedMediumOption, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'medium',
|
||||
requestUrl: source.options.medium,
|
||||
requestUrl: parsedMediumOption,
|
||||
responseStatus: response.status,
|
||||
});
|
||||
const feed = await parseFeed(xml);
|
||||
@@ -87,18 +98,11 @@ export const getMediumFeed = async (
|
||||
* to try to get the favicon when the source is created the first time.
|
||||
*/
|
||||
if (source.id === '' && feed.links.length > 0) {
|
||||
const favicon = await getFavicon(
|
||||
feed.links[0],
|
||||
(favicons: Favicon[]): Favicon[] => {
|
||||
return favicons.filter((favicon) => {
|
||||
return favicon.url.startsWith('https://cdn-images');
|
||||
});
|
||||
},
|
||||
);
|
||||
const favicon = await feedutils.getFavicon(feed.links[0], faviconFilter);
|
||||
|
||||
if (favicon && favicon.url.startsWith('https://')) {
|
||||
source.icon = favicon.url;
|
||||
source.icon = await uploadSourceIcon(supabaseClient, source);
|
||||
source.icon = await feedutils.uploadSourceIcon(supabaseClient, source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,11 +115,12 @@ export const getMediumFeed = async (
|
||||
source.id = generateSourceId(
|
||||
source.userId,
|
||||
source.columnId,
|
||||
source.options.medium,
|
||||
parsedMediumOption,
|
||||
);
|
||||
}
|
||||
source.type = 'medium';
|
||||
source.title = feed.title.value;
|
||||
source.options = { medium: parsedMediumOption };
|
||||
if (feed.links.length > 0) {
|
||||
source.link = feed.links[0];
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,14 +7,13 @@ import { unescape } from 'lodash';
|
||||
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { uploadSourceIcon } from './utils/uploadFile.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import {
|
||||
FEEDDECK_SOURCE_NITTER_BASIC_AUTH,
|
||||
FEEDDECK_SOURCE_NITTER_INSTANCE,
|
||||
} from '../utils/constants.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
export const getNitterFeed = async (
|
||||
supabaseClient: SupabaseClient,
|
||||
@@ -32,7 +31,7 @@ export const getNitterFeed = async (
|
||||
* Get the RSS for the provided `nitter` username or search term. If a feed
|
||||
* doesn't contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(
|
||||
const response = await utils.fetchWithTimeout(
|
||||
nitterOptions.feedUrl,
|
||||
{
|
||||
headers: nitterOptions.isCustomInstance ? undefined : {
|
||||
@@ -43,7 +42,7 @@ export const getNitterFeed = async (
|
||||
5000,
|
||||
);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'nitter',
|
||||
requestUrl: nitterOptions.feedUrl,
|
||||
responseStatus: response.status,
|
||||
@@ -80,7 +79,7 @@ export const getNitterFeed = async (
|
||||
*/
|
||||
if (!source.icon && nitterOptions.isUsername && feed.image?.url) {
|
||||
source.icon = feed.image.url;
|
||||
source.icon = await uploadSourceIcon(supabaseClient, source);
|
||||
source.icon = await feedutils.uploadSourceIcon(supabaseClient, source);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +177,7 @@ const skipEntry = (
|
||||
* instance or a username or search term, where we have to use our own Nitter
|
||||
* instance.
|
||||
*/
|
||||
const parseNitterOptions = (
|
||||
export const parseNitterOptions = (
|
||||
options: string,
|
||||
): {
|
||||
feedUrl: string;
|
||||
|
||||
457
supabase/functions/_shared/feed/nitter_test.ts
Normal file
457
supabase/functions/_shared/feed/nitter_test.ts
Normal file
@@ -0,0 +1,457 @@
|
||||
import { assertEquals } from 'std/assert';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { getNitterFeed, parseNitterOptions } from './nitter.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const responseTag = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
||||
<channel>
|
||||
<atom:link href="http://nitter.feeddeck.app/search" rel="self" type="application/rss+xml" />
|
||||
<title>Search results for "kubernetes"</title>
|
||||
<link>http://nitter.feeddeck.app/search</link>
|
||||
<description>Twitter feed for: Search "kubernetes". Generated by nitter.feeddeck.app</description>
|
||||
<language>en-us</language>
|
||||
<ttl>40</ttl>
|
||||
<item>
|
||||
<title>RT by @lunacyyan: Kubernetes V1.27 : Safeguarding Pod with MemoryThrottlingFactor
|
||||
|
||||
Read more: https://faun.pub/kubernetes-v1-27-safeguarding-pod-with-memorythrottlingfactor-cfbccde10de</title>
|
||||
<dc:creator>@CloudIslamabad</dc:creator>
|
||||
<description><![CDATA[<p>Kubernetes V1.27 : Safeguarding Pod with MemoryThrottlingFactor<br>
|
||||
<br>
|
||||
Read more: <a href="https://faun.pub/kubernetes-v1-27-safeguarding-pod-with-memorythrottlingfactor-cfbccde10de">faun.pub/kubernetes-v1-27-sa…</a></p>]]></description>
|
||||
<pubDate>Sat, 09 Dec 2023 17:42:53 GMT</pubDate>
|
||||
<guid>http://nitter.feeddeck.app/CloudIslamabad/status/1733542748857483570#m</guid>
|
||||
<link>http://nitter.feeddeck.app/CloudIslamabad/status/1733542748857483570#m</link>
|
||||
</item>
|
||||
<item>
|
||||
<title>RT by @mochizuki875: お疲れ様です!
|
||||
明日はCNDT2023のCommunity LTにて、16:10からKubernetes Meetup Noviceが登壇させて頂きます!
|
||||
|
||||
ぜひ遊びに来ていただけると嬉しいです!!
|
||||
よろしくお願いいたします!
|
||||
https://event.cloudnativedays.jp/cndt2023/community_lt
|
||||
|
||||
#k8snovice #CNDT2023</title>
|
||||
<dc:creator>@URyo_0213</dc:creator>
|
||||
<description><![CDATA[<p>お疲れ様です!<br>
|
||||
明日はCNDT2023のCommunity LTにて、16:10からKubernetes Meetup Noviceが登壇させて頂きます!<br>
|
||||
<br>
|
||||
ぜひ遊びに来ていただけると嬉しいです!!<br>
|
||||
よろしくお願いいたします!<br>
|
||||
<a href="https://event.cloudnativedays.jp/cndt2023/community_lt">event.cloudnativedays.jp/cnd…</a><br>
|
||||
<br>
|
||||
<a href="http://nitter.feeddeck.app/search?q=%23k8snovice">#k8snovice</a> <a href="http://nitter.feeddeck.app/search?q=%23CNDT2023">#CNDT2023</a></p>]]></description>
|
||||
<pubDate>Sun, 10 Dec 2023 11:22:30 GMT</pubDate>
|
||||
<guid>http://nitter.feeddeck.app/URyo_0213/status/1733809409393099181#m</guid>
|
||||
<link>http://nitter.feeddeck.app/URyo_0213/status/1733809409393099181#m</link>
|
||||
</item>
|
||||
<item>
|
||||
<title>Did you pass the Kubernetes certified application developer (CKAD) exam on your first attempt?
|
||||
|
||||
#Kubernetes #devops #cka</title>
|
||||
<dc:creator>@AbdelVA</dc:creator>
|
||||
<description><![CDATA[<p>Did you pass the Kubernetes certified application developer (CKAD) exam on your first attempt?<br>
|
||||
<br>
|
||||
<a href="http://nitter.feeddeck.app/search?q=%23Kubernetes">#Kubernetes</a> <a href="http://nitter.feeddeck.app/search?q=%23devops">#devops</a> <a href="http://nitter.feeddeck.app/search?q=%23cka">#cka</a></p>]]></description>
|
||||
<pubDate>Sun, 10 Dec 2023 11:42:45 GMT</pubDate>
|
||||
<guid>http://nitter.feeddeck.app/AbdelVA/status/1733814503551197653#m</guid>
|
||||
<link>http://nitter.feeddeck.app/AbdelVA/status/1733814503551197653#m</link>
|
||||
</item>
|
||||
<item>
|
||||
<title>RT by @eliyyov: Your app,
|
||||
in a container,
|
||||
in a pod,
|
||||
in Kubernetes,
|
||||
as a YAML file
|
||||
with Helm Charts
|
||||
deployed with ArgoCD</title>
|
||||
<dc:creator>@memenetes</dc:creator>
|
||||
<description><![CDATA[<p>Your app,<br>
|
||||
in a container,<br>
|
||||
in a pod,<br>
|
||||
in Kubernetes,<br>
|
||||
as a YAML file<br>
|
||||
with Helm Charts<br>
|
||||
deployed with ArgoCD</p>
|
||||
<img src="http://nitter.feeddeck.app/pic/ext_tw_video_thumb%2F1730270796612788224%2Fpu%2Fimg%2Fn2of8rx2u0oXtVAw.jpg" style="max-width:250px;" />]]></description>
|
||||
<pubDate>Thu, 30 Nov 2023 17:01:23 GMT</pubDate>
|
||||
<guid>http://nitter.feeddeck.app/memenetes/status/1730270811548701061#m</guid>
|
||||
<link>http://nitter.feeddeck.app/memenetes/status/1730270811548701061#m</link>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
const responseUser = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
||||
<channel>
|
||||
<atom:link href="http://nitter.feeddeck.app/rico_berger/rss" rel="self" type="application/rss+xml" />
|
||||
<title>Rico Berger / @rico_berger</title>
|
||||
<link>http://nitter.feeddeck.app/rico_berger</link>
|
||||
<description>Twitter feed for: @rico_berger. Generated by nitter.feeddeck.app</description>
|
||||
<language>en-us</language>
|
||||
<ttl>40</ttl>
|
||||
<image>
|
||||
<title>Rico Berger / @rico_berger</title>
|
||||
<link>http://nitter.feeddeck.app/rico_berger</link>
|
||||
<url>http://nitter.feeddeck.app/pic/pbs.twimg.com%2Fprofile_images%2F1076066289511206912%2Fi8mug5EL_400x400.jpg</url>
|
||||
<width>128</width>
|
||||
<height>128</height>
|
||||
</image>
|
||||
<item>
|
||||
<title>RT by @rico_berger: Blog: Gateway API v1.0: GA Release - https://kubernetes.io/blog/2023/10/31/gateway-api-ga/ #Kubernetes</title>
|
||||
<dc:creator>@K8sContributors</dc:creator>
|
||||
<description><![CDATA[<p>Blog: Gateway API v1.0: GA Release - <a href="https://kubernetes.io/blog/2023/10/31/gateway-api-ga/">kubernetes.io/blog/2023/10/3…</a> <a href="http://nitter.feeddeck.app/search?q=%23Kubernetes">#Kubernetes</a></p>
|
||||
<img src="http://nitter.feeddeck.app/pic/card_img%2F1729729773448970240%2F7PcfwkrY%3Fformat%3Dpng%26name%3D420x420_2" style="max-width:250px;" />]]></description>
|
||||
<pubDate>Tue, 31 Oct 2023 18:42:00 GMT</pubDate>
|
||||
<guid>http://nitter.feeddeck.app/K8sContributors/status/1719424500591210559#m</guid>
|
||||
<link>http://nitter.feeddeck.app/K8sContributors/status/1719424500591210559#m</link>
|
||||
</item>
|
||||
<item>
|
||||
<title>RT by @rico_berger: We are excited to announce the launch of OpenTofu, an open source alternative to Terraform's widely used infrastructure as code provisioning tool.
|
||||
|
||||
Read the announcement:
|
||||
https://hubs.la/Q022JfPQ0
|
||||
#opensource #opentofu #ossummit</title>
|
||||
<dc:creator>@linuxfoundation</dc:creator>
|
||||
<description><![CDATA[<p>We are excited to announce the launch of OpenTofu, an open source alternative to Terraform's widely used infrastructure as code provisioning tool.<br>
|
||||
<br>
|
||||
Read the announcement:<br>
|
||||
<a href="https://hubs.la/Q022JfPQ0">hubs.la/Q022JfPQ0</a><br>
|
||||
<a href="http://nitter.feeddeck.app/search?q=%23opensource">#opensource</a> <a href="http://nitter.feeddeck.app/search?q=%23opentofu">#opentofu</a> <a href="http://nitter.feeddeck.app/search?q=%23ossummit">#ossummit</a></p>
|
||||
<img src="http://nitter.feeddeck.app/pic/media%2FF6c1nOIWIAAOSAy.jpg" style="max-width:250px;" />]]></description>
|
||||
<pubDate>Wed, 20 Sep 2023 07:00:00 GMT</pubDate>
|
||||
<guid>http://nitter.feeddeck.app/linuxfoundation/status/1704389933526299129#m</guid>
|
||||
<link>http://nitter.feeddeck.app/linuxfoundation/status/1704389933526299129#m</link>
|
||||
</item>
|
||||
<item>
|
||||
<title>RT by @rico_berger: 🎉🎉🎉 kubenav v4 is finally available and can be downloaded from the Apple App Store (https://apps.apple.com/us/app/kubenav/id1494512160) or Google Play (https://play.google.com/store/apps/details?id=io.kubenav.kubenav) 🥳🥳🥳 #Kubernetes</title>
|
||||
<dc:creator>@kubenav</dc:creator>
|
||||
<description><![CDATA[<p>🎉🎉🎉 kubenav v4 is finally available and can be downloaded from the Apple App Store (<a href="https://apps.apple.com/us/app/kubenav/id1494512160">apps.apple.com/us/app/kubena…</a>) or Google Play (<a href="https://play.google.com/store/apps/details?id=io.kubenav.kubenav">play.google.com/store/apps/d…</a>) 🥳🥳🥳 <a href="http://nitter.feeddeck.app/search?q=%23Kubernetes">#Kubernetes</a></p>
|
||||
<img src="http://nitter.feeddeck.app/pic/media%2FFnVbCgSXEBAg-am.jpg" style="max-width:250px;" />
|
||||
<img src="http://nitter.feeddeck.app/pic/media%2FFnVbCgOXEAk3St9.jpg" style="max-width:250px;" />
|
||||
<img src="http://nitter.feeddeck.app/pic/media%2FFnVbCgOXEAYOZFK.jpg" style="max-width:250px;" />
|
||||
<img src="http://nitter.feeddeck.app/pic/media%2FFnVbH9hXEAEsd4I.jpg" style="max-width:250px;" />]]></description>
|
||||
<pubDate>Wed, 25 Jan 2023 17:29:00 GMT</pubDate>
|
||||
<guid>http://nitter.feeddeck.app/kubenav/status/1618299915129720832#m</guid>
|
||||
<link>http://nitter.feeddeck.app/kubenav/status/1618299915129720832#m</link>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
Deno.test('parseNitterOptions', () => {
|
||||
assertEquals(
|
||||
parseNitterOptions('https://nitter.net/rico_berger/rss'),
|
||||
{
|
||||
feedUrl: 'https://nitter.net/rico_berger/rss',
|
||||
sourceTitle: '@rico_berger',
|
||||
isUsername: true,
|
||||
isCustomInstance: true,
|
||||
},
|
||||
);
|
||||
assertEquals(
|
||||
parseNitterOptions('https://nitter.net/search/rss?f=tweets&q=kubernetes'),
|
||||
{
|
||||
feedUrl: 'https://nitter.net/search/rss?f=tweets&q=kubernetes',
|
||||
sourceTitle: 'kubernetes',
|
||||
isUsername: false,
|
||||
isCustomInstance: true,
|
||||
},
|
||||
);
|
||||
assertEquals(
|
||||
parseNitterOptions('@rico_berger'),
|
||||
{
|
||||
feedUrl: '/rico_berger/rss',
|
||||
sourceTitle: '@rico_berger',
|
||||
isUsername: true,
|
||||
isCustomInstance: false,
|
||||
},
|
||||
);
|
||||
assertEquals(
|
||||
parseNitterOptions('kubernetes'),
|
||||
{
|
||||
feedUrl: '/search/rss?f=tweets&q=kubernetes',
|
||||
sourceTitle: 'kubernetes',
|
||||
isUsername: false,
|
||||
isCustomInstance: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test('getNitterFeed - Tag', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseTag, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getNitterFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: {
|
||||
nitter: 'https://nitter.net/search/rss?f=tweets&q=kubernetes',
|
||||
},
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'nitter',
|
||||
'title': 'kubernetes',
|
||||
'options': {
|
||||
'nitter': 'https://nitter.net/search/rss?f=tweets&q=kubernetes',
|
||||
},
|
||||
'link': 'http://nitter.feeddeck.app/search',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e-e15978ba81b685189dfb89c07aa969db',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e',
|
||||
'title':
|
||||
'RT by @lunacyyan: Kubernetes V1.27 : Safeguarding Pod with MemoryThrottlingFactor\n\nRead more: https://faun.pub/kubernetes-v1-27-safeguarding-pod-with-memorythrottlingfactor-cfbccde10de',
|
||||
'link':
|
||||
'http://nitter.feeddeck.app/CloudIslamabad/status/1733542748857483570#m',
|
||||
'description':
|
||||
'<p>Kubernetes V1.27 : Safeguarding Pod with MemoryThrottlingFactor<br>\n<br>\nRead more: <a href="https://faun.pub/kubernetes-v1-27-safeguarding-pod-with-memorythrottlingfactor-cfbccde10de">faun.pub/kubernetes-v1-27-sa…</a></p>',
|
||||
'author': '@CloudIslamabad',
|
||||
'publishedAt': 1702143773,
|
||||
}, {
|
||||
'id':
|
||||
'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e-34df7ecc085e3e91133b6912c0775e03',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e',
|
||||
'title':
|
||||
'RT by @mochizuki875: お疲れ様です!\n明日はCNDT2023のCommunity LTにて、16:10からKubernetes Meetup Noviceが登壇させて頂きます!\n\nぜひ遊びに来ていただけると嬉しいです!!\nよろしくお願いいたします!\nhttps://event.cloudnativedays.jp/cndt2023/community_lt\n\n#k8snovice #CNDT2023',
|
||||
'link':
|
||||
'http://nitter.feeddeck.app/URyo_0213/status/1733809409393099181#m',
|
||||
'description':
|
||||
'<p>お疲れ様です!<br>\n明日はCNDT2023のCommunity LTにて、16:10からKubernetes Meetup Noviceが登壇させて頂きます!<br>\n<br>\nぜひ遊びに来ていただけると嬉しいです!!<br>\nよろしくお願いいたします!<br>\n<a href="https://event.cloudnativedays.jp/cndt2023/community_lt">event.cloudnativedays.jp/cnd…</a><br>\n<br>\n<a href="http://nitter.feeddeck.app/search?q=%23k8snovice">#k8snovice</a> <a href="http://nitter.feeddeck.app/search?q=%23CNDT2023">#CNDT2023</a></p>',
|
||||
'author': '@URyo_0213',
|
||||
'publishedAt': 1702207350,
|
||||
}, {
|
||||
'id':
|
||||
'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e-475ee6bb2ed19e551d0547545ba4e5a0',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e',
|
||||
'title':
|
||||
'Did you pass the Kubernetes certified application developer (CKAD) exam on your first attempt?\n\n#Kubernetes #devops #cka',
|
||||
'link': 'http://nitter.feeddeck.app/AbdelVA/status/1733814503551197653#m',
|
||||
'description':
|
||||
'<p>Did you pass the Kubernetes certified application developer (CKAD) exam on your first attempt?<br>\n<br>\n<a href="http://nitter.feeddeck.app/search?q=%23Kubernetes">#Kubernetes</a> <a href="http://nitter.feeddeck.app/search?q=%23devops">#devops</a> <a href="http://nitter.feeddeck.app/search?q=%23cka">#cka</a></p>',
|
||||
'author': '@AbdelVA',
|
||||
'publishedAt': 1702208565,
|
||||
}, {
|
||||
'id':
|
||||
'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e-d07f5752845c01a8288c393109770f3c',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'nitter-myuser-mycolumn-14a83e961dc175e20f36e70373cbae6e',
|
||||
'title':
|
||||
'RT by @eliyyov: Your app,\nin a container,\nin a pod,\nin Kubernetes,\nas a YAML file\nwith Helm Charts\ndeployed with ArgoCD',
|
||||
'link':
|
||||
'http://nitter.feeddeck.app/memenetes/status/1730270811548701061#m',
|
||||
'options': {
|
||||
'media': [
|
||||
'https://nitter.feeddeck.app/pic/ext_tw_video_thumb%2F1730270796612788224%2Fpu%2Fimg%2Fn2of8rx2u0oXtVAw.jpg',
|
||||
],
|
||||
},
|
||||
'description':
|
||||
'<p>Your app,<br>\nin a container,<br>\nin a pod,<br>\nin Kubernetes,<br>\nas a YAML file<br>\nwith Helm Charts<br>\ndeployed with ArgoCD</p>\n<img src="http://nitter.feeddeck.app/pic/ext_tw_video_thumb%2F1730270796612788224%2Fpu%2Fimg%2Fn2of8rx2u0oXtVAw.jpg" style="max-width:250px;" />',
|
||||
'author': '@memenetes',
|
||||
'publishedAt': 1701363683,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: ['https://nitter.net/search/rss?f=tweets&q=kubernetes', {
|
||||
headers: undefined,
|
||||
method: 'get',
|
||||
}, 5000],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseTag, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
|
||||
Deno.test('getNitterFeed - User', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseUser, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
const uploadSourceIconSpy = stub(
|
||||
feedutils,
|
||||
'uploadSourceIcon',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(
|
||||
'https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png',
|
||||
);
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getNitterFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: { nitter: 'https://nitter.net/rico_berger/rss' },
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'nitter-myuser-mycolumn-2f69b5c79645e7868dbefdee825bbb90',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'nitter',
|
||||
'title': '@rico_berger',
|
||||
'options': { 'nitter': 'https://nitter.net/rico_berger/rss' },
|
||||
'link': 'http://nitter.feeddeck.app/rico_berger',
|
||||
'icon':
|
||||
'https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'nitter-myuser-mycolumn-2f69b5c79645e7868dbefdee825bbb90-ed3ded7ee38f30e9c7e5e9f181ed96c2',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'nitter-myuser-mycolumn-2f69b5c79645e7868dbefdee825bbb90',
|
||||
'title':
|
||||
'RT by @rico_berger: Blog: Gateway API v1.0: GA Release - https://kubernetes.io/blog/2023/10/31/gateway-api-ga/ #Kubernetes',
|
||||
'link':
|
||||
'http://nitter.feeddeck.app/K8sContributors/status/1719424500591210559#m',
|
||||
'options': {
|
||||
'media': [
|
||||
'https://nitter.feeddeck.app/pic/card_img%2F1729729773448970240%2F7PcfwkrY%3Fformat%3Dpng%26name%3D420x420_2',
|
||||
],
|
||||
},
|
||||
'description':
|
||||
'<p>Blog: Gateway API v1.0: GA Release - <a href="https://kubernetes.io/blog/2023/10/31/gateway-api-ga/">kubernetes.io/blog/2023/10/3…</a> <a href="http://nitter.feeddeck.app/search?q=%23Kubernetes">#Kubernetes</a></p>\n<img src="http://nitter.feeddeck.app/pic/card_img%2F1729729773448970240%2F7PcfwkrY%3Fformat%3Dpng%26name%3D420x420_2" style="max-width:250px;" />',
|
||||
'author': '@K8sContributors',
|
||||
'publishedAt': 1698777720,
|
||||
}, {
|
||||
'id':
|
||||
'nitter-myuser-mycolumn-2f69b5c79645e7868dbefdee825bbb90-d185c4fa2b5b22eb0ea0cfd5de56802a',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'nitter-myuser-mycolumn-2f69b5c79645e7868dbefdee825bbb90',
|
||||
'title':
|
||||
'RT by @rico_berger: We are excited to announce the launch of OpenTofu, an open source alternative to Terraform\'s widely used infrastructure as code provisioning tool.\n\nRead the announcement:\nhttps://hubs.la/Q022JfPQ0\n#opensource #opentofu #ossummit',
|
||||
'link':
|
||||
'http://nitter.feeddeck.app/linuxfoundation/status/1704389933526299129#m',
|
||||
'options': {
|
||||
'media': [
|
||||
'https://nitter.feeddeck.app/pic/media%2FF6c1nOIWIAAOSAy.jpg',
|
||||
],
|
||||
},
|
||||
'description':
|
||||
'<p>We are excited to announce the launch of OpenTofu, an open source alternative to Terraform\'s widely used infrastructure as code provisioning tool.<br>\n<br>\nRead the announcement:<br>\n<a href="https://hubs.la/Q022JfPQ0">hubs.la/Q022JfPQ0</a><br>\n<a href="http://nitter.feeddeck.app/search?q=%23opensource">#opensource</a> <a href="http://nitter.feeddeck.app/search?q=%23opentofu">#opentofu</a> <a href="http://nitter.feeddeck.app/search?q=%23ossummit">#ossummit</a></p>\n<img src="http://nitter.feeddeck.app/pic/media%2FF6c1nOIWIAAOSAy.jpg" style="max-width:250px;" />',
|
||||
'author': '@linuxfoundation',
|
||||
'publishedAt': 1695193200,
|
||||
}, {
|
||||
'id':
|
||||
'nitter-myuser-mycolumn-2f69b5c79645e7868dbefdee825bbb90-708a757b055af5aa824ef6c361a4f30c',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'nitter-myuser-mycolumn-2f69b5c79645e7868dbefdee825bbb90',
|
||||
'title':
|
||||
'RT by @rico_berger: 🎉🎉🎉 kubenav v4 is finally available and can be downloaded from the Apple App Store (https://apps.apple.com/us/app/kubenav/id1494512160) or Google Play (https://play.google.com/store/apps/details?id=io.kubenav.kubenav) 🥳🥳🥳 #Kubernetes',
|
||||
'link': 'http://nitter.feeddeck.app/kubenav/status/1618299915129720832#m',
|
||||
'options': {
|
||||
'media': [
|
||||
'https://nitter.feeddeck.app/pic/media%2FFnVbCgSXEBAg-am.jpg',
|
||||
'https://nitter.feeddeck.app/pic/media%2FFnVbCgOXEAk3St9.jpg',
|
||||
'https://nitter.feeddeck.app/pic/media%2FFnVbCgOXEAYOZFK.jpg',
|
||||
'https://nitter.feeddeck.app/pic/media%2FFnVbH9hXEAEsd4I.jpg',
|
||||
],
|
||||
},
|
||||
'description':
|
||||
'<p>🎉🎉🎉 kubenav v4 is finally available and can be downloaded from the Apple App Store (<a href="https://apps.apple.com/us/app/kubenav/id1494512160">apps.apple.com/us/app/kubena…</a>) or Google Play (<a href="https://play.google.com/store/apps/details?id=io.kubenav.kubenav">play.google.com/store/apps/d…</a>) 🥳🥳🥳 <a href="http://nitter.feeddeck.app/search?q=%23Kubernetes">#Kubernetes</a></p>\n<img src="http://nitter.feeddeck.app/pic/media%2FFnVbCgSXEBAg-am.jpg" style="max-width:250px;" />\n<img src="http://nitter.feeddeck.app/pic/media%2FFnVbCgOXEAk3St9.jpg" style="max-width:250px;" />\n<img src="http://nitter.feeddeck.app/pic/media%2FFnVbCgOXEAYOZFK.jpg" style="max-width:250px;" />\n<img src="http://nitter.feeddeck.app/pic/media%2FFnVbH9hXEAEsd4I.jpg" style="max-width:250px;" />',
|
||||
'author': '@kubenav',
|
||||
'publishedAt': 1674667740,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
uploadSourceIconSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: ['https://nitter.net/rico_berger/rss', {
|
||||
headers: undefined,
|
||||
method: 'get',
|
||||
}, 5000],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseUser, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCall(uploadSourceIconSpy, 0, {
|
||||
args: [
|
||||
supabaseClient,
|
||||
{
|
||||
'id': 'nitter-myuser-mycolumn-2f69b5c79645e7868dbefdee825bbb90',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'nitter',
|
||||
'title': '@rico_berger',
|
||||
'options': { 'nitter': 'https://nitter.net/rico_berger/rss' },
|
||||
'link': 'http://nitter.feeddeck.app/rico_berger',
|
||||
'icon':
|
||||
'https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png',
|
||||
},
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(
|
||||
'https://media.hachyderm.io/accounts/avatars/109/773/619/675/865/785/original/bf731ded4166a661.png',
|
||||
);
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
assertSpyCalls(uploadSourceIconSpy, 1);
|
||||
});
|
||||
@@ -8,8 +8,7 @@ import { unescape } from 'lodash';
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
const pinterestUrls = [
|
||||
'pinterest.at',
|
||||
@@ -55,19 +54,12 @@ export const isPinterestUrl = (url: string): boolean => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getPinterestFeed = async (
|
||||
_supabaseClient: SupabaseClient,
|
||||
_redisClient: Redis | undefined,
|
||||
_profile: IProfile,
|
||||
source: ISource,
|
||||
): Promise<{ source: ISource; items: IItem[] }> => {
|
||||
/**
|
||||
* Since the `pinterest` option supports multiple input format we need to
|
||||
* normalize it to a valid Pinterest feed url. If this is not possible we
|
||||
* consider the provided option as invalid.
|
||||
*/
|
||||
if (source.options?.pinterest) {
|
||||
const input = source.options.pinterest;
|
||||
/**
|
||||
* `parsePinterestOption` parses the provided `input` and returns a valid
|
||||
* Pinterest RSS feed url.
|
||||
*/
|
||||
export const parsePinterestOption = (input?: string): string => {
|
||||
if (input) {
|
||||
/**
|
||||
* If the input starts with `@` we assume that a username or board was
|
||||
* provided in the form of `@username` or `@username/board`. We then use
|
||||
@@ -75,13 +67,9 @@ export const getPinterestFeed = async (
|
||||
*/
|
||||
if (input.length > 1 && input[0] === '@') {
|
||||
if (input.includes('/')) {
|
||||
source.options.pinterest = `https://www.pinterest.com/${
|
||||
input.substring(1)
|
||||
}.rss`;
|
||||
return `https://www.pinterest.com/${input.substring(1)}.rss`;
|
||||
} else {
|
||||
source.options.pinterest = `https://www.pinterest.com/${
|
||||
input.substring(1)
|
||||
}/feed.rss`;
|
||||
return `https://www.pinterest.com/${input.substring(1)}/feed.rss`;
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
@@ -101,18 +89,16 @@ export const getPinterestFeed = async (
|
||||
pinterestDotComUrl.endsWith('.rss') ||
|
||||
pinterestDotComUrl.endsWith('/feed.rss')
|
||||
) {
|
||||
source.options.pinterest = pinterestDotComUrl;
|
||||
return pinterestDotComUrl;
|
||||
} else {
|
||||
const urlParameters = pinterestDotComUrl.replace(
|
||||
'https://www.pinterest.com/',
|
||||
'',
|
||||
).replace(/\/$/, '');
|
||||
if (urlParameters.includes('/')) {
|
||||
source.options.pinterest =
|
||||
`https://www.pinterest.com/${urlParameters}.rss`;
|
||||
return `https://www.pinterest.com/${urlParameters}.rss`;
|
||||
} else {
|
||||
source.options.pinterest =
|
||||
`https://www.pinterest.com/${urlParameters}/feed.rss`;
|
||||
return `https://www.pinterest.com/${urlParameters}/feed.rss`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -122,18 +108,27 @@ export const getPinterestFeed = async (
|
||||
} else {
|
||||
throw new Error('Invalid source options');
|
||||
}
|
||||
};
|
||||
|
||||
export const getPinterestFeed = async (
|
||||
_supabaseClient: SupabaseClient,
|
||||
_redisClient: Redis | undefined,
|
||||
_profile: IProfile,
|
||||
source: ISource,
|
||||
): Promise<{ source: ISource; items: IItem[] }> => {
|
||||
const parsedPinterestOption = parsePinterestOption(source.options?.pinterest);
|
||||
|
||||
/**
|
||||
* Get the RSS for the provided `pinterest` url and parse it. If a feed
|
||||
* doesn't contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.pinterest, {
|
||||
const response = await utils.fetchWithTimeout(parsedPinterestOption, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'pinterest',
|
||||
requestUrl: source.options.pinterest,
|
||||
requestUrl: parsedPinterestOption,
|
||||
responseStatus: response.status,
|
||||
});
|
||||
const feed = await parseFeed(xml);
|
||||
@@ -151,11 +146,12 @@ export const getPinterestFeed = async (
|
||||
source.id = generateSourceId(
|
||||
source.userId,
|
||||
source.columnId,
|
||||
source.options.pinterest,
|
||||
parsedPinterestOption,
|
||||
);
|
||||
}
|
||||
source.type = 'pinterest';
|
||||
source.title = feed.title.value;
|
||||
source.options = { pinterest: parsedPinterestOption };
|
||||
if (feed.links.length > 0) {
|
||||
source.link = feed.links[0];
|
||||
}
|
||||
@@ -199,7 +195,7 @@ export const getPinterestFeed = async (
|
||||
media: getMedia(entry),
|
||||
description: getItemDescription(entry),
|
||||
author: `@${
|
||||
source.options.pinterest.replace('https://www.pinterest.com/', '')
|
||||
parsedPinterestOption.replace('https://www.pinterest.com/', '')
|
||||
.replace('.rss', '').replace('/feed.rss', '').split('/')[0]
|
||||
}`,
|
||||
publishedAt: Math.floor(entry.published!.getTime() / 1000),
|
||||
|
||||
188
supabase/functions/_shared/feed/pinterest_test.ts
Normal file
188
supabase/functions/_shared/feed/pinterest_test.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { assertEquals, assertThrows } from 'std/assert';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import {
|
||||
getPinterestFeed,
|
||||
isPinterestUrl,
|
||||
parsePinterestOption,
|
||||
} from './pinterest.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const responseUser = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||
<channel>
|
||||
<title>Pinterest Deutschland</title>
|
||||
<link>https://www.pinterest.com/pinterestde/</link>
|
||||
<description>Pinterest ist deine App voller Ideen zum Entdecken, Selbermachen, Ausprobieren.</description>
|
||||
<atom:link href="https://www.pinterest.de/pinterestde/feed.rss" rel="self" />
|
||||
<language>en-us</language>
|
||||
<lastBuildDate>Sun, 10 Dec 2023 16:33:00 GMT</lastBuildDate>
|
||||
<item>
|
||||
<title>Willy Wonka und die Schokoladenfabril Warner Kinofilm Photo Edit | Photshop #DezemberChallenge</title>
|
||||
<link>https://www.pinterest.de/pin/458452437083858830/</link>
|
||||
<description><a href="https://www.pinterest.de/pin/458452437083858830/"><img src="https://i.pinimg.com/236x/38/ec/d5/38ecd5a70f26b7aed5a03d5d3d202a90.jpg"></a>Willy Wonka und die Schokoladenfabril Warner Kinofilm Photo Edit | Photshop #DezemberChallenge</description>
|
||||
<pubDate>Thu, 07 Dec 2023 17:28:35 GMT</pubDate>
|
||||
<guid>https://www.pinterest.de/pin/458452437083858830/</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title />
|
||||
<link>https://www.pinterest.de/pin/458452437083858826/</link>
|
||||
<description><a href="https://www.pinterest.de/pin/458452437083858826/"><img src="https://i.pinimg.com/236x/bd/fe/c1/bdfec1ee5d5342c250460e7065cb037f.jpg"></a></description>
|
||||
<pubDate>Thu, 07 Dec 2023 17:28:01 GMT</pubDate>
|
||||
<guid>https://www.pinterest.de/pin/458452437083858826/</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title />
|
||||
<link>https://www.pinterest.de/pin/458452437083858815/</link>
|
||||
<description><a href="https://www.pinterest.de/pin/458452437083858815/"><img src="https://i.pinimg.com/236x/bb/b6/8b/bbb68be1c4069ba9c1facdcf66063849.jpg"></a></description>
|
||||
<pubDate>Thu, 07 Dec 2023 17:26:56 GMT</pubDate>
|
||||
<guid>https://www.pinterest.de/pin/458452437083858815/</guid>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
Deno.test('isPinterestUrl', () => {
|
||||
assertEquals(
|
||||
isPinterestUrl('https://www.pinterest.de/pinterestde/essen-und-trinken/'),
|
||||
true,
|
||||
);
|
||||
assertEquals(isPinterestUrl('https://www.google.de/'), false);
|
||||
});
|
||||
|
||||
Deno.test('parsePinterestOption', () => {
|
||||
assertEquals(
|
||||
parsePinterestOption('https://www.pinterest.de/pinterestde'),
|
||||
'https://www.pinterest.com/pinterestde/feed.rss',
|
||||
);
|
||||
assertEquals(
|
||||
parsePinterestOption('https://www.pinterest.de/aurelianstoian/math/'),
|
||||
'https://www.pinterest.com/aurelianstoian/math.rss',
|
||||
);
|
||||
assertEquals(
|
||||
parsePinterestOption('@pinterestde'),
|
||||
'https://www.pinterest.com/pinterestde/feed.rss',
|
||||
);
|
||||
assertEquals(
|
||||
parsePinterestOption('@aurelianstoian/math'),
|
||||
'https://www.pinterest.com/aurelianstoian/math.rss',
|
||||
);
|
||||
assertThrows(() => parsePinterestOption(undefined));
|
||||
assertThrows(() => parsePinterestOption(''));
|
||||
});
|
||||
|
||||
Deno.test('getPinterestFeed - User', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseUser, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getPinterestFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: { pinterest: 'https://www.pinterest.de/pinterestde' },
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'pinterest-myuser-mycolumn-18a73e1c3eb363440dbd64cfa9dfd1ab',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'pinterest',
|
||||
'title': 'Pinterest Deutschland',
|
||||
'options': {
|
||||
'pinterest': 'https://www.pinterest.com/pinterestde/feed.rss',
|
||||
},
|
||||
'link': 'https://www.pinterest.com/pinterestde/',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'pinterest-myuser-mycolumn-18a73e1c3eb363440dbd64cfa9dfd1ab-b943f6a54297d4cab0bb47da53a3966a',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'pinterest-myuser-mycolumn-18a73e1c3eb363440dbd64cfa9dfd1ab',
|
||||
'title':
|
||||
'Willy Wonka und die Schokoladenfabril Warner Kinofilm Photo Edit | Photshop #DezemberChallenge',
|
||||
'link': 'https://www.pinterest.de/pin/458452437083858830/',
|
||||
'media':
|
||||
'https://i.pinimg.com/236x/38/ec/d5/38ecd5a70f26b7aed5a03d5d3d202a90.jpg',
|
||||
'description':
|
||||
'<a href="https://www.pinterest.de/pin/458452437083858830/"><img src="https://i.pinimg.com/236x/38/ec/d5/38ecd5a70f26b7aed5a03d5d3d202a90.jpg"></a>Willy Wonka und die Schokoladenfabril Warner Kinofilm Photo Edit | Photshop #DezemberChallenge',
|
||||
'author': '@pinterestde',
|
||||
'publishedAt': 1701970115,
|
||||
}, {
|
||||
'id':
|
||||
'pinterest-myuser-mycolumn-18a73e1c3eb363440dbd64cfa9dfd1ab-3bf66585db7320d3182510e8a691350c',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'pinterest-myuser-mycolumn-18a73e1c3eb363440dbd64cfa9dfd1ab',
|
||||
'title': '',
|
||||
'link': 'https://www.pinterest.de/pin/458452437083858826/',
|
||||
'media':
|
||||
'https://i.pinimg.com/236x/bd/fe/c1/bdfec1ee5d5342c250460e7065cb037f.jpg',
|
||||
'description':
|
||||
'<a href="https://www.pinterest.de/pin/458452437083858826/"><img src="https://i.pinimg.com/236x/bd/fe/c1/bdfec1ee5d5342c250460e7065cb037f.jpg"></a>',
|
||||
'author': '@pinterestde',
|
||||
'publishedAt': 1701970081,
|
||||
}, {
|
||||
'id':
|
||||
'pinterest-myuser-mycolumn-18a73e1c3eb363440dbd64cfa9dfd1ab-b604f0bd41e5f641231a99b9606854a3',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'pinterest-myuser-mycolumn-18a73e1c3eb363440dbd64cfa9dfd1ab',
|
||||
'title': '',
|
||||
'link': 'https://www.pinterest.de/pin/458452437083858815/',
|
||||
'media':
|
||||
'https://i.pinimg.com/236x/bb/b6/8b/bbb68be1c4069ba9c1facdcf66063849.jpg',
|
||||
'description':
|
||||
'<a href="https://www.pinterest.de/pin/458452437083858815/"><img src="https://i.pinimg.com/236x/bb/b6/8b/bbb68be1c4069ba9c1facdcf66063849.jpg"></a>',
|
||||
'author': '@pinterestde',
|
||||
'publishedAt': 1701970016,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://www.pinterest.com/pinterestde/feed.rss',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseUser, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
@@ -7,10 +7,9 @@ import { unescape } from 'lodash';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { uploadSourceIcon } from './utils/uploadFile.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
export const getPodcastFeed = async (
|
||||
supabaseClient: SupabaseClient,
|
||||
@@ -38,11 +37,11 @@ export const getPodcastFeed = async (
|
||||
* Get the RSS for the provided `podcast` url and parse it. If a feed doesn't
|
||||
* contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.podcast, {
|
||||
const response = await utils.fetchWithTimeout(source.options.podcast, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'podcast',
|
||||
requestUrl: source.options.podcast,
|
||||
responseStatus: response.status,
|
||||
@@ -75,12 +74,12 @@ export const getPodcastFeed = async (
|
||||
if (!source.icon) {
|
||||
if (feed.image?.url) {
|
||||
source.icon = feed.image.url;
|
||||
source.icon = await uploadSourceIcon(supabaseClient, source);
|
||||
source.icon = await feedutils.uploadSourceIcon(supabaseClient, source);
|
||||
// deno-lint-ignore no-explicit-any
|
||||
} else if ((feed as any)['itunes:image']?.href) {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
source.icon = (feed as any)['itunes:image'].href;
|
||||
source.icon = await uploadSourceIcon(supabaseClient, source);
|
||||
source.icon = await feedutils.uploadSourceIcon(supabaseClient, source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +172,7 @@ const skipEntry = (
|
||||
* Podcast id.
|
||||
*/
|
||||
const getRSSFeedFromApplePodcast = async (id: string): Promise<string> => {
|
||||
const resp = await fetchWithTimeout(
|
||||
const resp = await utils.fetchWithTimeout(
|
||||
`https://itunes.apple.com/lookup?id=${id}&entity=podcast`,
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
|
||||
504
supabase/functions/_shared/feed/podcast_test.ts
Normal file
504
supabase/functions/_shared/feed/podcast_test.ts
Normal file
@@ -0,0 +1,504 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { getPodcastFeed } from './podcast.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
import { assertEqualsItems, assertEqualsSource } from './utils/test.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const responsePodcastRSS = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:cc="http://web.resource.org/cc/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" version="2.0">
|
||||
<channel>
|
||||
<atom:link href="https://feeds.libsyn.com/419861/rss" rel="self" type="application/rss+xml" />
|
||||
<title>Kubernetes Podcast from Google</title>
|
||||
<pubDate>Tue, 05 Dec 2023 23:37:00 +0000</pubDate>
|
||||
<lastBuildDate>Wed, 06 Dec 2023 09:03:41 +0000</lastBuildDate>
|
||||
<generator>Libsyn WebEngine 2.0</generator>
|
||||
<link>https://kubernetespodcast.com</link>
|
||||
<language>en-us</language>
|
||||
<copyright><![CDATA[This work is licensed under a Creative Commons License - Attribution-NonCommercial-NoDerivatives 4.0 International - http://creativecommons.org/licenses/by-nc-nd/4.0/]]></copyright>
|
||||
<docs>https://kubernetespodcast.com</docs>
|
||||
<managingEditor>kubernetespodcast@google.com (kubernetespodcast@google.com)</managingEditor>
|
||||
<itunes:summary><![CDATA[A weekly podcast focused on what's happening in the Kubernetes community hosted by Abdel Sghiouar and Kaslin Fields. We cover Kubernetes, cloud-native applications, and other developments in the ecosystem. Abdel and Kaslin on Twitter at @KubernetesPod or by email at kubernetespodcast@google.com.]]></itunes:summary>
|
||||
<image>
|
||||
<url>https://static.libsyn.com/p/assets/7/2/d/d/72ddd143dc0d42c316c3140a3186d450/Kubernetes-Podcast-Logo_1400x1400.png</url>
|
||||
<title>Kubernetes Podcast from Google</title>
|
||||
<link><![CDATA[https://kubernetespodcast.com]]></link>
|
||||
</image>
|
||||
<itunes:author>Abdel Sghiouar, Kaslin Fields</itunes:author>
|
||||
<itunes:category text="News">
|
||||
<itunes:category text="Tech News" />
|
||||
</itunes:category>
|
||||
<itunes:category text="Technology" />
|
||||
<itunes:image href="https://static.libsyn.com/p/assets/7/2/d/d/72ddd143dc0d42c316c3140a3186d450/Kubernetes-Podcast-Logo_1400x1400.png" />
|
||||
<itunes:explicit>false</itunes:explicit>
|
||||
<itunes:owner>
|
||||
<itunes:name><![CDATA[Kubernetes Podcast from Google]]></itunes:name>
|
||||
<itunes:email>kubernetespodcast@google.com</itunes:email>
|
||||
</itunes:owner>
|
||||
<description><![CDATA[A weekly podcast focused on what's happening in the Kubernetes community hosted by Abdel Sghiouar and Kaslin Fields. We cover Kubernetes, cloud-native applications, and other developments in the ecosystem. Abdel and Kaslin on Twitter at @KubernetesPod or by email at kubernetespodcast@google.com.]]></description>
|
||||
<itunes:type>episodic</itunes:type>
|
||||
<!-- START CHANNEL EXTRA TAGS -->
|
||||
<itunes:new-feed-url>https://feeds.libsyn.com/419861/rss</itunes:new-feed-url>
|
||||
<!-- CLOSE CHANNEL EXTRA TAGS -->
|
||||
<podcast:locked owner="kubernetespodcast@google.com">no</podcast:locked>
|
||||
<item>
|
||||
<title>KubeCon NA 2023</title>
|
||||
<itunes:title>KubeCon NA 2023</itunes:title>
|
||||
<pubDate>Tue, 05 Dec 2023 23:37:00 +0000</pubDate>
|
||||
<guid isPermaLink="false"><![CDATA[5f0f56c3-6ba9-4479-9eff-c1a23d058e06]]></guid>
|
||||
<link><![CDATA[http://sites.libsyn.com/419861/kubecon-na-2023]]></link>
|
||||
<itunes:image href="https://static.libsyn.com/p/assets/3/1/3/a/313a88f0d39a0ede88c4a68c3ddbc4f2/NewKPodRoboWhite_2-20231205-ukhaijcnr3.png" />
|
||||
<description><![CDATA[<p dir="ltr">This episode Kaslin went to KubeCon North America In Chicago. She spoke to folks on the ground, asked them about their impressions of the conference, and collected a bunch of cool responses.</p> <p dir="ltr">Do you have something cool to share? Some questions? Let us know:</p> <p dir="ltr">- web: <a href= "https://kubernetespodcast.com">kubernetespodcast.com</a></p> <p dir="ltr">- mail: <a href= "mailto:kubernetespodcast@google.com">kubernetespodcast@google.com</a></p> <p dir="ltr">- twitter: <a href= "https://twitter.com/kubernetespod">@kubernetespod</a></p> <h2 dir="ltr">News of the week</h2> <p dir="ltr"><a href= "https://cloud.google.com/blog/products/identity-security/google-researchers-discover-reptar-a-new-cpu-vulnerability"> Google researchers discover 'Reptar,’ a new CPU vulnerability</a></p> <p dir="ltr"><a href= "https://lock.cmpxchg8b.com/reptar.html">Reptar by Tavis Ormandy</a></p> <p dir="ltr"><a href= "https://thenewstack.io/tim-hockin-kubernetes-needs-a-complexity-budget/"> Tim Hockin: Kubernetes Needs a Complexity Budget</a></p> <p dir="ltr"><a href= "https://www.theregister.com/2023/11/13/kubernetes_tim_hockin_on_ai/"> Kubernetes' Tim Hockin on a decade of dominance and the future of AI in open source</a> </p> <p dir="ltr"><a href= "https://www.youtube.com/watch?v=WqeShpaztZY&list=PLj6h78yzYM2MYc0X1465RzF_7Cqf7bnqL&index=19"> Keynote: A Vision for Vision - Kubernetes in Its Second Decade - Tim Hockin</a></p> <p dir="ltr"><a href= "https://github.com/cncf/tag-security/blob/main/assessments/Open_and_Secure.pdf"> Open and Secure: A Manual for Practicing Thread Modeling to Assess and Fortify Open Source and Security</a></p> <p dir="ltr"><a href= "https://www.cncf.io/blog/2023/11/22/announcing-our-latest-book-release-a-comprehensive-security-guide-to-assess-and-fortify-open-source-security/"> Announcing our latest book release: a comprehensive security guide to assess and fortify open source security</a></p> <h2 dir="ltr">Links from the interview</h2> <p dir="ltr"><a href= "https://github.com/cncf/llm-starter-pack">CNCF LLM Starter Pack</a></p> <p dir="ltr"><a href= "https://www.crossplane.io/">Crossplane</a></p> <p dir="ltr"><a href="https://webassembly.org/">Web Assembly</a></p> <p dir="ltr"><a href="https://gateway-api.sigs.k8s.io/">Intro to Kubernetes Gateway API</a></p> <h2 dir="ltr">Links from the post-interview chat</h2> <p dir="ltr"><a href= "https://twitter.com/birthmarkbart/status/1389960932395294725?s=20"> SIG ContribEx Comms Team Rap by Bart Farrell</a></p>]]></description>
|
||||
<content:encoded><![CDATA[<p dir="ltr">This episode Kaslin went to KubeCon North America In Chicago. She spoke to folks on the ground, asked them about their impressions of the conference, and collected a bunch of cool responses.</p> <p dir="ltr">Do you have something cool to share? Some questions? Let us know:</p> <p dir="ltr">- web: <a href= "https://kubernetespodcast.com">kubernetespodcast.com</a></p> <p dir="ltr">- mail: <a href= "mailto:kubernetespodcast@google.com">kubernetespodcast@google.com</a></p> <p dir="ltr">- twitter: <a href= "https://twitter.com/kubernetespod">@kubernetespod</a></p> News of the week <p dir="ltr"><a href= "https://cloud.google.com/blog/products/identity-security/google-researchers-discover-reptar-a-new-cpu-vulnerability"> Google researchers discover 'Reptar,’ a new CPU vulnerability</a></p> <p dir="ltr"><a href= "https://lock.cmpxchg8b.com/reptar.html">Reptar by Tavis Ormandy</a></p> <p dir="ltr"><a href= "https://thenewstack.io/tim-hockin-kubernetes-needs-a-complexity-budget/"> Tim Hockin: Kubernetes Needs a Complexity Budget</a></p> <p dir="ltr"><a href= "https://www.theregister.com/2023/11/13/kubernetes_tim_hockin_on_ai/"> Kubernetes' Tim Hockin on a decade of dominance and the future of AI in open source</a> </p> <p dir="ltr"><a href= "https://www.youtube.com/watch?v=WqeShpaztZY&list=PLj6h78yzYM2MYc0X1465RzF_7Cqf7bnqL&index=19"> Keynote: A Vision for Vision - Kubernetes in Its Second Decade - Tim Hockin</a></p> <p dir="ltr"><a href= "https://github.com/cncf/tag-security/blob/main/assessments/Open_and_Secure.pdf"> Open and Secure: A Manual for Practicing Thread Modeling to Assess and Fortify Open Source and Security</a></p> <p dir="ltr"><a href= "https://www.cncf.io/blog/2023/11/22/announcing-our-latest-book-release-a-comprehensive-security-guide-to-assess-and-fortify-open-source-security/"> Announcing our latest book release: a comprehensive security guide to assess and fortify open source security</a></p> Links from the interview <p dir="ltr"><a href= "https://github.com/cncf/llm-starter-pack">CNCF LLM Starter Pack</a></p> <p dir="ltr"><a href= "https://www.crossplane.io/">Crossplane</a></p> <p dir="ltr"><a href="https://webassembly.org/">Web Assembly</a></p> <p dir="ltr"><a href="https://gateway-api.sigs.k8s.io/">Intro to Kubernetes Gateway API</a></p> Links from the post-interview chat <p dir="ltr"><a href= "https://twitter.com/birthmarkbart/status/1389960932395294725?s=20"> SIG ContribEx Comms Team Rap by Bart Farrell</a></p>]]></content:encoded>
|
||||
<enclosure length="79018008" type="audio/mpeg" url="https://traffic.libsyn.com/secure/e780d51f-f115-44a6-8252-aed9216bb521/KPOD214.mp3?dest-id=3486674" />
|
||||
<itunes:duration>54:53</itunes:duration>
|
||||
<itunes:explicit>false</itunes:explicit>
|
||||
<itunes:keywords />
|
||||
<itunes:subtitle><![CDATA[This episode Kaslin went to KubeCon North America In Chicago. She spoke to folks on the ground, asked them about their impressions of the conference, and collected a bunch of cool responses. Do you have something cool to share? Some questions? Let us...]]></itunes:subtitle>
|
||||
<itunes:episode>214</itunes:episode>
|
||||
<itunes:episodeType>full</itunes:episodeType>
|
||||
</item>
|
||||
<item>
|
||||
<title>Kubernetes Pen Testing, with Jesper Larsson</title>
|
||||
<itunes:title>Kubernetes Pen Testing, with Jesper Larsson</itunes:title>
|
||||
<pubDate>Wed, 29 Nov 2023 00:18:00 +0000</pubDate>
|
||||
<guid isPermaLink="false"><![CDATA[1451620d-4a53-4522-9ad1-978600a7331f]]></guid>
|
||||
<link><![CDATA[http://sites.libsyn.com/419861/kubernetes-pen-testing-with-jesper-larsson]]></link>
|
||||
<itunes:image href="https://static.libsyn.com/p/assets/3/7/1/2/37123d51ad937e05e5bbc093207a2619/NewKPodRoboWhite_2-20231129-1q4wg71lhl.png" />
|
||||
<description><![CDATA[<p dir="ltr">Jesper Larsson is a Freelance PenTester. Jesper works with a hacker community called Cure53. Co-organizes SecurityFest in Gothenburg, Sweden. Hosts Säkerhetspodcasten or The Security Podcast. Jesper is also a Star on Hackad, a Swedish TV Series about hacking.</p> <p><strong> </strong></p> <p dir="ltr">Do you have something cool to share? Some questions? Let us know:</p> <p dir="ltr">- web: <a href= "https://kubernetespodcast.com">kubernetespodcast.com</a></p> <p dir="ltr">- mail: <a href= "mailto:kubernetespodcast@google.com">kubernetespodcast@google.com</a></p> <p dir="ltr">- twitter: <a href= "https://twitter.com/kubernetespod">@kubernetespod</a></p> <p><strong> </strong></p> <h2 dir="ltr">News of the week</h2> <p dir="ltr"><a href= "https://kubernetes.io/blog/2023/11/16/kubernetes-1-29-upcoming-changes/"> Kubernetes Removals, Deprecations, and Major Changes in Kubernetes 1.29</a></p> <p dir="ltr"><a href= "https://kubernetes.io/blog/2023/11/07/introducing-sig-etcd/">Introducing SIG etcd</a></p> <p dir="ltr"><a href= "https://kubernetespodcast.com/episode/211-etcd/">etcd, with Marek Siarkowicz and Wenjia Zhang</a> (The Kubernetes Podcast from Google)</p> <p dir="ltr"><a href= "https://cloud.redhat.com/blog/webassembly-wasm-and-openshift-a-powerful-duo-for-modern-applications"> WebAssembly (WASM) and OpenShift: A Powerful Duo for Modern Applications</a></p> <p dir="ltr"><a href="https://events.linuxfoundation.org/">Linux Foundation Events</a></p> <p dir="ltr"><a href= "https://github.com/kubernetes/community/pull/7603">Pass the torch in ContribEx #7603</a></p> <h2 dir="ltr">Links from the interview</h2> <p dir="ltr"><a href="https://cure53.de/">Cure53 Hacker Community</a></p> <p dir="ltr"><a href= "https://sakerhetspodcasten.se/">Säkerhetspodcasten</a></p> <p dir="ltr"><a href= "https://www.imdb.com/title/tt15746988/">Hackad TV Show on IMDB</a></p> <p dir="ltr"><a href="https://securityfest.com/">SecurityFest Gothenburg</a></p> <p dir="ltr"><a href="https://falco.org/">Falco</a> by <a href= "https://sysdig.com/">Sysdig</a></p> <p dir="ltr"><a href="https://github.com/wolfi-dev">Wolfi</a> by <a href="https://www.chainguard.dev/">Chainguard</a></p> <p dir="ltr"><a href= "https://www.wired.com/story/notpetya-cyberattack-ukraine-russia-code-crashed-the-world/"> The Untold Story of NotPetya, the Most Devastating Cyberattack in History</a></p> <h2 dir="ltr">Links from the post-interview chat</h2> <p dir="ltr"><a href= "https://www.wired.com/story/notpetya-cyberattack-ukraine-russia-code-crashed-the-world/"> The Untold Story of NotPetya, the Most Devastating Cyberattack in History</a></p>]]></description>
|
||||
<content:encoded><![CDATA[<p dir="ltr">Jesper Larsson is a Freelance PenTester. Jesper works with a hacker community called Cure53. Co-organizes SecurityFest in Gothenburg, Sweden. Hosts Säkerhetspodcasten or The Security Podcast. Jesper is also a Star on Hackad, a Swedish TV Series about hacking.</p> <p> </p> <p dir="ltr">Do you have something cool to share? Some questions? Let us know:</p> <p dir="ltr">- web: <a href= "https://kubernetespodcast.com">kubernetespodcast.com</a></p> <p dir="ltr">- mail: <a href= "mailto:kubernetespodcast@google.com">kubernetespodcast@google.com</a></p> <p dir="ltr">- twitter: <a href= "https://twitter.com/kubernetespod">@kubernetespod</a></p> <p> </p> News of the week <p dir="ltr"><a href= "https://kubernetes.io/blog/2023/11/16/kubernetes-1-29-upcoming-changes/"> Kubernetes Removals, Deprecations, and Major Changes in Kubernetes 1.29</a></p> <p dir="ltr"><a href= "https://kubernetes.io/blog/2023/11/07/introducing-sig-etcd/">Introducing SIG etcd</a></p> <p dir="ltr"><a href= "https://kubernetespodcast.com/episode/211-etcd/">etcd, with Marek Siarkowicz and Wenjia Zhang</a> (The Kubernetes Podcast from Google)</p> <p dir="ltr"><a href= "https://cloud.redhat.com/blog/webassembly-wasm-and-openshift-a-powerful-duo-for-modern-applications"> WebAssembly (WASM) and OpenShift: A Powerful Duo for Modern Applications</a></p> <p dir="ltr"><a href="https://events.linuxfoundation.org/">Linux Foundation Events</a></p> <p dir="ltr"><a href= "https://github.com/kubernetes/community/pull/7603">Pass the torch in ContribEx #7603</a></p> Links from the interview <p dir="ltr"><a href="https://cure53.de/">Cure53 Hacker Community</a></p> <p dir="ltr"><a href= "https://sakerhetspodcasten.se/">Säkerhetspodcasten</a></p> <p dir="ltr"><a href= "https://www.imdb.com/title/tt15746988/">Hackad TV Show on IMDB</a></p> <p dir="ltr"><a href="https://securityfest.com/">SecurityFest Gothenburg</a></p> <p dir="ltr"><a href="https://falco.org/">Falco</a> by <a href= "https://sysdig.com/">Sysdig</a></p> <p dir="ltr"><a href="https://github.com/wolfi-dev">Wolfi</a> by <a href="https://www.chainguard.dev/">Chainguard</a></p> <p dir="ltr"><a href= "https://www.wired.com/story/notpetya-cyberattack-ukraine-russia-code-crashed-the-world/"> The Untold Story of NotPetya, the Most Devastating Cyberattack in History</a></p> Links from the post-interview chat <p dir="ltr"><a href= "https://www.wired.com/story/notpetya-cyberattack-ukraine-russia-code-crashed-the-world/"> The Untold Story of NotPetya, the Most Devastating Cyberattack in History</a></p>]]></content:encoded>
|
||||
<enclosure length="73742318" type="audio/mpeg" url="https://traffic.libsyn.com/secure/e780d51f-f115-44a6-8252-aed9216bb521/KPOD213.mp3?dest-id=3486674" />
|
||||
<itunes:duration>51:13</itunes:duration>
|
||||
<itunes:explicit>false</itunes:explicit>
|
||||
<itunes:keywords />
|
||||
<itunes:subtitle><![CDATA[Jesper Larsson is a Freelance PenTester. Jesper works with a hacker community called Cure53. Co-organizes SecurityFest in Gothenburg, Sweden. Hosts Säkerhetspodcasten or The Security Podcast. Jesper is also a Star on Hackad, a Swedish TV Series about...]]></itunes:subtitle>
|
||||
<itunes:episode>213</itunes:episode>
|
||||
<itunes:episodeType>full</itunes:episodeType>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
Deno.test('getPodcastFeed - RSS', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responsePodcastRSS, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const uploadSourceIconSpy = stub(
|
||||
feedutils,
|
||||
'uploadSourceIcon',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(
|
||||
'https://static.libsyn.com/p/assets/7/2/d/d/72ddd143dc0d42c316c3140a3186d450/Kubernetes-Podcast-Logo_1400x1400.png',
|
||||
);
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getPodcastFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: { podcast: 'https://kubernetespodcast.com/feeds/audio.xml' },
|
||||
},
|
||||
);
|
||||
assertEqualsSource(source, {
|
||||
'id': 'podcast-myuser-mycolumn-9d151d96e51e542b848a39982f685eef',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'podcast',
|
||||
'title': 'Kubernetes Podcast from Google',
|
||||
'options': { 'podcast': 'https://kubernetespodcast.com/feeds/audio.xml' },
|
||||
'link': 'https://kubernetespodcast.com',
|
||||
'icon':
|
||||
'https://static.libsyn.com/p/assets/7/2/d/d/72ddd143dc0d42c316c3140a3186d450/Kubernetes-Podcast-Logo_1400x1400.png',
|
||||
});
|
||||
assertEqualsItems(items, [{
|
||||
'id':
|
||||
'podcast-myuser-mycolumn-9d151d96e51e542b848a39982f685eef-b7986c0276dcd01cdce685b148530a99',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'podcast-myuser-mycolumn-9d151d96e51e542b848a39982f685eef',
|
||||
'title': 'KubeCon NA 2023',
|
||||
'link': 'http://sites.libsyn.com/419861/kubecon-na-2023',
|
||||
'media':
|
||||
'https://traffic.libsyn.com/secure/e780d51f-f115-44a6-8252-aed9216bb521/KPOD214.mp3?dest-id=3486674',
|
||||
'description':
|
||||
'<p dir="ltr">This episode Kaslin went to KubeCon North America In Chicago. She spoke to folks on the ground, asked them about their impressions of the conference, and collected a bunch of cool responses.</p> <p dir="ltr">Do you have something cool to share? Some questions? Let us know:</p> <p dir="ltr">- web: <a href= "https://kubernetespodcast.com">kubernetespodcast.com</a></p> <p dir="ltr">- mail: <a href= "mailto:kubernetespodcast@google.com">kubernetespodcast@google.com</a></p> <p dir="ltr">- twitter: <a href= "https://twitter.com/kubernetespod">@kubernetespod</a></p> <h2 dir="ltr">News of the week</h2> <p dir="ltr"><a href= "https://cloud.google.com/blog/products/identity-security/google-researchers-discover-reptar-a-new-cpu-vulnerability"> Google researchers discover \'Reptar,’ a new CPU vulnerability</a></p> <p dir="ltr"><a href= "https://lock.cmpxchg8b.com/reptar.html">Reptar by Tavis Ormandy</a></p> <p dir="ltr"><a href= "https://thenewstack.io/tim-hockin-kubernetes-needs-a-complexity-budget/"> Tim Hockin: Kubernetes Needs a Complexity Budget</a></p> <p dir="ltr"><a href= "https://www.theregister.com/2023/11/13/kubernetes_tim_hockin_on_ai/"> Kubernetes\' Tim Hockin on a decade of dominance and the future of AI in open source</a> </p> <p dir="ltr"><a href= "https://www.youtube.com/watch?v=WqeShpaztZY&list=PLj6h78yzYM2MYc0X1465RzF_7Cqf7bnqL&index=19"> Keynote: A Vision for Vision - Kubernetes in Its Second Decade - Tim Hockin</a></p> <p dir="ltr"><a href= "https://github.com/cncf/tag-security/blob/main/assessments/Open_and_Secure.pdf"> Open and Secure: A Manual for Practicing Thread Modeling to Assess and Fortify Open Source and Security</a></p> <p dir="ltr"><a href= "https://www.cncf.io/blog/2023/11/22/announcing-our-latest-book-release-a-comprehensive-security-guide-to-assess-and-fortify-open-source-security/"> Announcing our latest book release: a comprehensive security guide to assess and fortify open source security</a></p> <h2 dir="ltr">Links from the interview</h2> <p dir="ltr"><a href= "https://github.com/cncf/llm-starter-pack">CNCF LLM Starter Pack</a></p> <p dir="ltr"><a href= "https://www.crossplane.io/">Crossplane</a></p> <p dir="ltr"><a href="https://webassembly.org/">Web Assembly</a></p> <p dir="ltr"><a href="https://gateway-api.sigs.k8s.io/">Intro to Kubernetes Gateway API</a></p> <h2 dir="ltr">Links from the post-interview chat</h2> <p dir="ltr"><a href= "https://twitter.com/birthmarkbart/status/1389960932395294725?s=20"> SIG ContribEx Comms Team Rap by Bart Farrell</a></p>',
|
||||
'publishedAt': 1701819420,
|
||||
}, {
|
||||
'id':
|
||||
'podcast-myuser-mycolumn-9d151d96e51e542b848a39982f685eef-4c7c98567f8fbe488d28b0cd032f7a72',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'podcast-myuser-mycolumn-9d151d96e51e542b848a39982f685eef',
|
||||
'title': 'Kubernetes Pen Testing, with Jesper Larsson',
|
||||
'link':
|
||||
'http://sites.libsyn.com/419861/kubernetes-pen-testing-with-jesper-larsson',
|
||||
'media':
|
||||
'https://traffic.libsyn.com/secure/e780d51f-f115-44a6-8252-aed9216bb521/KPOD213.mp3?dest-id=3486674',
|
||||
'description':
|
||||
'<p dir="ltr">Jesper Larsson is a Freelance PenTester. Jesper works with a hacker community called Cure53. Co-organizes SecurityFest in Gothenburg, Sweden. Hosts Säkerhetspodcasten or The Security Podcast. Jesper is also a Star on Hackad, a Swedish TV Series about hacking.</p> <p><strong> </strong></p> <p dir="ltr">Do you have something cool to share? Some questions? Let us know:</p> <p dir="ltr">- web: <a href= "https://kubernetespodcast.com">kubernetespodcast.com</a></p> <p dir="ltr">- mail: <a href= "mailto:kubernetespodcast@google.com">kubernetespodcast@google.com</a></p> <p dir="ltr">- twitter: <a href= "https://twitter.com/kubernetespod">@kubernetespod</a></p> <p><strong> </strong></p> <h2 dir="ltr">News of the week</h2> <p dir="ltr"><a href= "https://kubernetes.io/blog/2023/11/16/kubernetes-1-29-upcoming-changes/"> Kubernetes Removals, Deprecations, and Major Changes in Kubernetes 1.29</a></p> <p dir="ltr"><a href= "https://kubernetes.io/blog/2023/11/07/introducing-sig-etcd/">Introducing SIG etcd</a></p> <p dir="ltr"><a href= "https://kubernetespodcast.com/episode/211-etcd/">etcd, with Marek Siarkowicz and Wenjia Zhang</a> (The Kubernetes Podcast from Google)</p> <p dir="ltr"><a href= "https://cloud.redhat.com/blog/webassembly-wasm-and-openshift-a-powerful-duo-for-modern-applications"> WebAssembly (WASM) and OpenShift: A Powerful Duo for Modern Applications</a></p> <p dir="ltr"><a href="https://events.linuxfoundation.org/">Linux Foundation Events</a></p> <p dir="ltr"><a href= "https://github.com/kubernetes/community/pull/7603">Pass the torch in ContribEx #7603</a></p> <h2 dir="ltr">Links from the interview</h2> <p dir="ltr"><a href="https://cure53.de/">Cure53 Hacker Community</a></p> <p dir="ltr"><a href= "https://sakerhetspodcasten.se/">Säkerhetspodcasten</a></p> <p dir="ltr"><a href= "https://www.imdb.com/title/tt15746988/">Hackad TV Show on IMDB</a></p> <p dir="ltr"><a href="https://securityfest.com/">SecurityFest Gothenburg</a></p> <p dir="ltr"><a href="https://falco.org/">Falco</a> by <a href= "https://sysdig.com/">Sysdig</a></p> <p dir="ltr"><a href="https://github.com/wolfi-dev">Wolfi</a> by <a href="https://www.chainguard.dev/">Chainguard</a></p> <p dir="ltr"><a href= "https://www.wired.com/story/notpetya-cyberattack-ukraine-russia-code-crashed-the-world/"> The Untold Story of NotPetya, the Most Devastating Cyberattack in History</a></p> <h2 dir="ltr">Links from the post-interview chat</h2> <p dir="ltr"><a href= "https://www.wired.com/story/notpetya-cyberattack-ukraine-russia-code-crashed-the-world/"> The Untold Story of NotPetya, the Most Devastating Cyberattack in History</a></p>',
|
||||
'publishedAt': 1701217080,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
uploadSourceIconSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://kubernetespodcast.com/feeds/audio.xml',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responsePodcastRSS, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCall(uploadSourceIconSpy, 0, {
|
||||
args: [
|
||||
supabaseClient,
|
||||
{
|
||||
'id': 'podcast-myuser-mycolumn-9d151d96e51e542b848a39982f685eef',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'podcast',
|
||||
'title': 'Kubernetes Podcast from Google',
|
||||
'options': {
|
||||
'podcast': 'https://kubernetespodcast.com/feeds/audio.xml',
|
||||
},
|
||||
'link': 'https://kubernetespodcast.com',
|
||||
'icon':
|
||||
'https://static.libsyn.com/p/assets/7/2/d/d/72ddd143dc0d42c316c3140a3186d450/Kubernetes-Podcast-Logo_1400x1400.png',
|
||||
},
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(
|
||||
'https://static.libsyn.com/p/assets/7/2/d/d/72ddd143dc0d42c316c3140a3186d450/Kubernetes-Podcast-Logo_1400x1400.png',
|
||||
);
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
assertSpyCalls(uploadSourceIconSpy, 1);
|
||||
});
|
||||
|
||||
const responsePodcastApple = `{
|
||||
"resultCount":1,
|
||||
"results": [
|
||||
{"wrapperType":"track", "kind":"podcast", "collectionId":1120964487, "trackId":1120964487, "artistName":"Changelog Media", "collectionName":"Go Time: Golang, Software Engineering", "trackName":"Go Time: Golang, Software Engineering", "collectionCensoredName":"Go Time: Golang, Software Engineering", "trackCensoredName":"Go Time: Golang, Software Engineering", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/go-time-golang-software-engineering/id1120964487?uo=4", "feedUrl":"https://changelog.com/gotime/feed", "trackViewUrl":"https://podcasts.apple.com/us/podcast/go-time-golang-software-engineering/id1120964487?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts113/v4/0d/25/86/0d258649-4cfe-2750-6316-dffd9dbe3b8d/mza_5183216603141582042.png/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts113/v4/0d/25/86/0d258649-4cfe-2750-6316-dffd9dbe3b8d/mza_5183216603141582042.png/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts113/v4/0d/25/86/0d258649-4cfe-2750-6316-dffd9dbe3b8d/mza_5183216603141582042.png/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2023-11-08T13:15:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":304, "trackTimeMillis":5264, "country":"USA", "currency":"USD", "primaryGenreName":"Technology", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts113/v4/0d/25/86/0d258649-4cfe-2750-6316-dffd9dbe3b8d/mza_5183216603141582042.png/600x600bb.jpg", "genreIds":["1318", "26", "1304", "1499"], "genres":["Technology", "Podcasts", "Education", "How To"]}]
|
||||
}`;
|
||||
const responsePodcastAppleRSS = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://podcastindex.org/namespace/1.0" version="2.0">
|
||||
<channel>
|
||||
<title>Go Time: Golang, Software Engineering</title>
|
||||
<copyright>All rights reserved</copyright>
|
||||
<link>https://changelog.com/gotime</link>
|
||||
<atom:link href="https://changelog.com/gotime/feed" rel="self" type="application/rss+xml" />
|
||||
<atom:link href="https://changelog.com/gotime" rel="alternate" type="text/html" />
|
||||
<language>en-us</language>
|
||||
<description>Your source for diverse discussions from around the Go community. This show records LIVE every Tuesday at 3pm US Eastern. Join the Golang community and chat with us during the show in the #gotimefm channel of Gophers slack. Panelists include Mat Ryer, Jon Calhoun, Natalie Pistunovich, Johnny Boursiquot, Angelica Hill, Kris Brandow, and Ian Lopshire. We discuss cloud infrastructure, distributed systems, microservices, Kubernetes, Docker… oh and also Go! Some people search for GoTime or GoTimeFM and can’t find the show, so now the strings GoTime and GoTimeFM are in our description too.</description>
|
||||
<itunes:author>Changelog Media</itunes:author>
|
||||
<itunes:summary>Your source for diverse discussions from around the Go community. This show records LIVE every Tuesday at 3pm US Eastern. Join the Golang community and chat with us during the show in the #gotimefm channel of Gophers slack. Panelists include Mat Ryer, Jon Calhoun, Natalie Pistunovich, Johnny Boursiquot, Angelica Hill, Kris Brandow, and Ian Lopshire. We discuss cloud infrastructure, distributed systems, microservices, Kubernetes, Docker… oh and also Go! Some people search for GoTime or GoTimeFM and can’t find the show, so now the strings GoTime and GoTimeFM are in our description too.</itunes:summary>
|
||||
<itunes:explicit>no</itunes:explicit>
|
||||
<itunes:image href="https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357" />
|
||||
<itunes:owner>
|
||||
<itunes:name>Changelog Media</itunes:name>
|
||||
</itunes:owner>
|
||||
<itunes:keywords>go, golang, open source, software, development, devops, architecture, docker, kubernetes</itunes:keywords>
|
||||
<itunes:category text="Technology">
|
||||
<itunes:category text="Software How-To" />
|
||||
<itunes:category text="Tech News" />
|
||||
</itunes:category>
|
||||
<podcast:funding url="https://changelog.com/++">Support our work by joining Changelog++</podcast:funding>
|
||||
<podcast:person role="host" img="https://cdn.changelog.com/uploads/avatars/people/lbY/avatar_large.jpg?v=63764553668" href="https://changelog.com/person/matryer">Mat Ryer</podcast:person>
|
||||
<podcast:person role="host" img="https://secure.gravatar.com/avatar/1777de319efe52999139231273746dc9.jpg?s=600&d=mm" href="https://changelog.com/person/joncalhoun">Jon Calhoun</podcast:person>
|
||||
<podcast:person role="host" img="https://cdn.changelog.com/uploads/avatars/people/M0oR/avatar_large.jpeg?v=63729653215" href="https://changelog.com/person/nataliepis">Natalie Pistunovich</podcast:person>
|
||||
<podcast:person role="host" img="https://secure.gravatar.com/avatar/6d26a33d20b8e96182b8e71c30ffe927.jpg?s=600&d=mm" href="https://changelog.com/person/jboursiquot">Johnny Boursiquot</podcast:person>
|
||||
<podcast:person role="host" img="https://cdn.changelog.com/uploads/avatars/people/wwOp4/avatar_large.jpg?v=63772335237" href="https://changelog.com/person/angelicahill">Angelica Hill</podcast:person>
|
||||
<podcast:person role="host" img="https://cdn.changelog.com/uploads/avatars/people/Qyme/avatar_large.jpg?v=63758939850" href="https://changelog.com/person/skriptble">Kris Brandow</podcast:person>
|
||||
<podcast:person role="host" img="https://cdn.changelog.com/uploads/avatars/people/yWWz5/avatar_large.jpg?v=63779509228" href="https://changelog.com/person/ianlopshire">Ian Lopshire</podcast:person>
|
||||
<item>
|
||||
<title>Event-driven systems &architecture</title>
|
||||
<link>https://changelog.com/gotime/297</link>
|
||||
<guid isPermaLink="false">changelog.com/2/2126</guid>
|
||||
<pubDate>Tue, 14 Nov 2023 22:05:00 +0000</pubDate>
|
||||
<enclosure url="https://op3.dev/e/https://cdn.changelog.com/uploads/gotime/297/go-time-297.mp3" length="63299111" type="audio/mpeg" />
|
||||
<description>Event-driven systems may not be the go-to solution for everyone because of the challenges they can add. While the system reacting to events published in other parts of the system seem elegant, some of the complexities they bring can be challenging. However, they do offer durability, autonomy &flexibility. In this episode, we’ll define event-driven architecture, discuss the problems it solves, challenges it poses &potential solutions.</description>
|
||||
<content:encoded><![CDATA[<p>Event-driven systems may not be the go-to solution for everyone because of the challenges they can add. While the system reacting to events published in other parts of the system seem elegant, some of the complexities they bring can be challenging. However, they do offer durability, autonomy & flexibility.</p>
|
||||
<p>In this episode, we’ll define event-driven architecture, discuss the problems it solves, challenges it poses & potential solutions.</p>
|
||||
|
||||
<p><a href="https://changelog.com/gotime/297/discuss">Leave us a comment</a></p>
|
||||
|
||||
<p><a href="https://changelog.com/++" rel="payment">Changelog++</a> members save 1 minute on this episode because they made the ads disappear. Join today!</p>
|
||||
|
||||
<p>Sponsors:</p>
|
||||
<p><ul>
|
||||
<li><a href="https://fastly.com/?utm_source=changelog">Fastly</a> – <strong>Our bandwidth partner.</strong> Fastly powers fast, secure, and scalable digital experiences. Move beyond your content delivery network to their powerful edge cloud platform. Learn more at <a href="https://www.fastly.com/?utm_source=changelog&utm_medium=podcast&utm_campaign=changelog-sponsorship">fastly.com</a>
|
||||
</li><li><a href="https://fly.io/changelog">Fly.io</a> – <strong>The home of Changelog.com</strong> — Deploy your apps and databases close to your users. In minutes you can run your Ruby, Go, Node, Deno, Python, or Elixir app (and databases!) all over the world. No ops required. Learn more at <a href="https://fly.io/changelog">fly.io/changelog</a> and check out <a href="https://fly.io/docs/speedrun/">the speedrun in their docs</a>.
|
||||
</li><li><a href="https://cloud.typesense.org/?utm_source=changelog">Typesense</a> – Lightning fast, globally distributed Search-as-a-Service that runs in memory. You literally can’t get any faster!
|
||||
</li>
|
||||
</ul></p>
|
||||
|
||||
|
||||
<p>Featuring:</p>
|
||||
<p><ul><li>Chris Richardson – <a href="https://fosstodon.org/@crichardson" rel="external ugc">Mastodon</a>, <a href="https://twitter.com/crichardson" rel="external ugc">Twitter</a>, <a href="https://www.linkedin.com/in/pojos" rel="external ugc">LinkedIn</a></li><li>Indu Alagarsamy – <a href="https://twitter.com/indu_alagarsamy" rel="external ugc">Twitter</a>, <a href="https://www.linkedin.com/in/indualagarsamy" rel="external ugc">LinkedIn</a>, <a href="https://indu.dev" rel="external ugc">Website</a></li><li>Viktor Stanchev – <a href="https://twitter.com/vikstrous" rel="external ugc">Twitter</a>, <a href="https://github.com/vikstrous" rel="external ugc">GitHub</a>, <a href="https://www.linkedin.com/in/viktorstanchev" rel="external ugc">LinkedIn</a>, <a href="https://viktorstanchev.com/" rel="external ugc">Website</a></li><li>Angelica Hill – <a href="https://twitter.com/Angelica_Hill" rel="external ugc">Twitter</a>, <a href="https://github.com/angelicahill" rel="external ugc">GitHub</a></li></ul></p>
|
||||
|
||||
<p>Show Notes:</p>
|
||||
<p><ul>
|
||||
<li><a href="https://temporal.io/">Temporal</a></li>
|
||||
<li><a href="https://microservices.io/patterns/data/transactional-outbox.html">Patterns</a></li>
|
||||
<li><a href="https://microservices.io/patterns/data/saga.html">Choreography vs orchestration</a></li>
|
||||
<li><a href="https://martinfowler.com/eaaDev/EventSourcing.html">Event sourcing</a></li>
|
||||
<li><a href="https://microservices.io/patterns/data/cqrs.html">CQRS</a></li>
|
||||
<li>Cloud-based workflow solutions
|
||||
<ul>
|
||||
<li><a href="https://cloud.google.com/workflows">GCP Workflows</a>
|
||||
<ul>
|
||||
<li><a href="https://cloud.google.com/workflows/docs/reference/syntax/syntax-cheat-sheet">GCP workflows yaml language</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="https://aws.amazon.com/step-functions/">AWS Step Functions</a>
|
||||
<ul>
|
||||
<li><a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-amazon-states-language.html">“Amazon states language” based on json</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Go specific microservices orchestration tools, frameworks, and libraries:
|
||||
<ul>
|
||||
<li><a href="https://github.com/temporalio/sdk-go">Temporal Go SDK</a></li>
|
||||
<li><a href="https://github.com/ThreeDotsLabs/watermill">Watermill</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>Something missing or broken? <a href="https://github.com/thechangelog/show-notes/blob/master/gotime/go-time-297.md">PRs welcome!</a></p>]]></content:encoded>
|
||||
<itunes:episodeType>full</itunes:episodeType>
|
||||
<itunes:image href="https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357" />
|
||||
<itunes:duration>1:05:24</itunes:duration>
|
||||
<itunes:explicit>no</itunes:explicit>
|
||||
<itunes:keywords>go, golang, open source, software, development, devops, architecture, docker, kubernetes</itunes:keywords>
|
||||
<itunes:subtitle>with Chris Richardson, Indu Alagarsamy &Viktor Stanchev</itunes:subtitle>
|
||||
<itunes:summary>Event-driven systems may not be the go-to solution for everyone because of the challenges they can add. While the system reacting to events published in other parts of the system seem elegant, some of the complexities they bring can be challenging. However, they do offer durability, autonomy &flexibility. In this episode, we’ll define event-driven architecture, discuss the problems it solves, challenges it poses &potential solutions.</itunes:summary>
|
||||
<dc:creator>Changelog Media</dc:creator>
|
||||
<itunes:author>Changelog Media</itunes:author>
|
||||
<podcast:person role="host" img="https://cdn.changelog.com/uploads/avatars/people/wwOp4/avatar_large.jpg?v=63772335237" href="https://changelog.com/person/angelicahill">Angelica Hill</podcast:person>
|
||||
<podcast:person role="guest" img="https://cdn.changelog.com/uploads/avatars/people/wwXpo/avatar_large.png?v=63859946508" href="https://changelog.com/person/chrisrichardson">Chris Richardson</podcast:person>
|
||||
<podcast:person role="guest" img="https://cdn.changelog.com/uploads/avatars/people/dVJwl/avatar_large.png?v=63859946700" href="https://changelog.com/person/indua">Indu Alagarsamy</podcast:person>
|
||||
<podcast:person role="guest" img="https://cdn.changelog.com/uploads/avatars/people/6DwkJ/avatar_large.jpg?v=63859946242" href="https://changelog.com/person/viktor">Viktor Stanchev</podcast:person>
|
||||
<podcast:chapters url="https://changelog.com/gotime/297/chapters" type="application/json+chapters" />
|
||||
<podcast:socialInteract uri="https://changelog.social/@gotime/111414814157362161" protocol="activitypub" />
|
||||
</item>
|
||||
<item>
|
||||
<title>Principles of simplicity</title>
|
||||
<link>https://changelog.com/gotime/296</link>
|
||||
<guid isPermaLink="false">changelog.com/2/2255</guid>
|
||||
<pubDate>Wed, 08 Nov 2023 13:15:00 +0000</pubDate>
|
||||
<enclosure url="https://op3.dev/e/https://cdn.changelog.com/uploads/gotime/296/go-time-296.mp3" length="84740716" type="audio/mpeg" />
|
||||
<description>Rob Pike says, “Simplicity is the art of hiding complexity.” If that’s true, what is simplicity in the context of writing software in Go? Is it even something we should strive for? Can software be too simple? Ian &Kris discuss with return guest sam boyer.</description>
|
||||
<content:encoded><![CDATA[<p>Rob Pike says, “Simplicity is the art of hiding complexity.” If that’s true, what is simplicity in the context of writing software in Go? Is it even something we should strive for? Can software be too simple? Ian & Kris discuss with return guest sam boyer.</p>
|
||||
|
||||
<p><a href="https://changelog.com/gotime/296/discuss">Leave us a comment</a></p>
|
||||
|
||||
<p><a href="https://changelog.com/++" rel="payment">Changelog++</a> members save 2 minutes on this episode because they made the ads disappear. Join today!</p>
|
||||
|
||||
<p>Sponsors:</p>
|
||||
<p><ul>
|
||||
<li><a href="https://changelog.com/news">Changelog News</a> – A podcast+newsletter combo that’s brief, entertaining & always on-point. <a href="https://changelog.com/news">Subscribe today</a>.
|
||||
</li><li><a href="https://fastly.com/?utm_source=changelog">Fastly</a> – <strong>Our bandwidth partner.</strong> Fastly powers fast, secure, and scalable digital experiences. Move beyond your content delivery network to their powerful edge cloud platform. Learn more at <a href="https://www.fastly.com/?utm_source=changelog&utm_medium=podcast&utm_campaign=changelog-sponsorship">fastly.com</a>
|
||||
</li><li><a href="https://fly.io/changelog">Fly.io</a> – <strong>The home of Changelog.com</strong> — Deploy your apps and databases close to your users. In minutes you can run your Ruby, Go, Node, Deno, Python, or Elixir app (and databases!) all over the world. No ops required. Learn more at <a href="https://fly.io/changelog">fly.io/changelog</a> and check out <a href="https://fly.io/docs/speedrun/">the speedrun in their docs</a>.
|
||||
</li>
|
||||
</ul></p>
|
||||
|
||||
|
||||
<p>Featuring:</p>
|
||||
<p><ul><li>sam boyer – <a href="https://twitter.com/sdboyer" rel="external ugc">Twitter</a>, <a href="https://github.com/sdboyer" rel="external ugc">GitHub</a></li><li>Ian Lopshire – <a href="https://twitter.com/ianlopshire" rel="external ugc">Twitter</a>, <a href="https://github.com/ianlopshire" rel="external ugc">GitHub</a></li><li>Kris Brandow – <a href="https://twitter.com/skriptble" rel="external ugc">Twitter</a>, <a href="https://github.com/skriptble" rel="external ugc">GitHub</a></li></ul></p>
|
||||
|
||||
<p>Show Notes:</p>
|
||||
<p><ul>
|
||||
<li><a href="https://www.youtube.com/watch?v=rFejpH_tAHM">Rob Pike - Simplicity is Complicated</a></li>
|
||||
<li><a href="https://simonsinek.com/books/the-infinite-game/">The Infinite Game - Simon Sinek</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=SxdOUGdseq4">“Simple Made Easy” - Rich Hickey (2011)</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=dF98ii6r_gU&t=190s">“You can’t get snakes from chicken eggs”</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>Something missing or broken? <a href="https://github.com/thechangelog/show-notes/blob/master/gotime/go-time-296.md">PRs welcome!</a></p>]]></content:encoded>
|
||||
<itunes:episodeType>full</itunes:episodeType>
|
||||
<itunes:image href="https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357" />
|
||||
<itunes:duration>1:27:44</itunes:duration>
|
||||
<itunes:explicit>no</itunes:explicit>
|
||||
<itunes:keywords>go, golang, open source, software, development, devops, architecture, docker, kubernetes</itunes:keywords>
|
||||
<itunes:subtitle>with sam boyer</itunes:subtitle>
|
||||
<itunes:summary>Rob Pike says, “Simplicity is the art of hiding complexity.” If that’s true, what is simplicity in the context of writing software in Go? Is it even something we should strive for? Can software be too simple? Ian &Kris discuss with return guest sam boyer.</itunes:summary>
|
||||
<dc:creator>Changelog Media</dc:creator>
|
||||
<itunes:author>Changelog Media</itunes:author>
|
||||
<podcast:person role="host" img="https://cdn.changelog.com/uploads/avatars/people/yWWz5/avatar_large.jpg?v=63779509228" href="https://changelog.com/person/ianlopshire">Ian Lopshire</podcast:person>
|
||||
<podcast:person role="host" img="https://cdn.changelog.com/uploads/avatars/people/Qyme/avatar_large.jpg?v=63758939850" href="https://changelog.com/person/skriptble">Kris Brandow</podcast:person>
|
||||
<podcast:person role="guest" img="https://secure.gravatar.com/avatar/029f1c16a31002fe48f73bdec52cc2e0.jpg?s=600&d=mm" href="https://changelog.com/person/sdboyer">sam boyer</podcast:person>
|
||||
<podcast:chapters url="https://changelog.com/gotime/296/chapters" type="application/json+chapters" />
|
||||
<podcast:socialInteract uri="https://changelog.social/@gotime/111380686496450294" protocol="activitypub" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
Deno.test('getPodcastFeed - Apple', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responsePodcastApple, { status: 200 }));
|
||||
}),
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responsePodcastAppleRSS, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const uploadSourceIconSpy = stub(
|
||||
feedutils,
|
||||
'uploadSourceIcon',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(
|
||||
'https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357',
|
||||
);
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getPodcastFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: {
|
||||
podcast:
|
||||
'https://podcasts.apple.com/de/podcast/go-time-golang-software-engineering/id1120964487',
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEqualsSource(source, {
|
||||
'id': 'podcast-myuser-mycolumn-aad37b7b4ebb1f79286d7b9e24bb4163',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'podcast',
|
||||
'title': 'Go Time: Golang, Software Engineering',
|
||||
'options': { 'podcast': 'https://changelog.com/gotime/feed' },
|
||||
'link': 'https://changelog.com/gotime',
|
||||
'icon':
|
||||
'https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357',
|
||||
});
|
||||
assertEqualsItems(items, [{
|
||||
'id':
|
||||
'podcast-myuser-mycolumn-aad37b7b4ebb1f79286d7b9e24bb4163-04c1e4407a4e70983deb0950f6afc8b2',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'podcast-myuser-mycolumn-aad37b7b4ebb1f79286d7b9e24bb4163',
|
||||
'title': 'Event-driven systems &architecture',
|
||||
'link': 'https://changelog.com/gotime/297',
|
||||
'media':
|
||||
'https://op3.dev/e/https://cdn.changelog.com/uploads/gotime/297/go-time-297.mp3',
|
||||
'description':
|
||||
'Event-driven systems may not be the go-to solution for everyone because of the challenges they can add. While the system reacting to events published in other parts of the system seem elegant, some of the complexities they bring can be challenging. However, they do offer durability, autonomy &flexibility. In this episode, we’ll define event-driven architecture, discuss the problems it solves, challenges it poses &potential solutions.',
|
||||
'author': 'Changelog Media',
|
||||
'publishedAt': 1699999500,
|
||||
}, {
|
||||
'id':
|
||||
'podcast-myuser-mycolumn-aad37b7b4ebb1f79286d7b9e24bb4163-2eb33370a9f9245ca2b1fc4491983383',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'podcast-myuser-mycolumn-aad37b7b4ebb1f79286d7b9e24bb4163',
|
||||
'title': 'Principles of simplicity',
|
||||
'link': 'https://changelog.com/gotime/296',
|
||||
'media':
|
||||
'https://op3.dev/e/https://cdn.changelog.com/uploads/gotime/296/go-time-296.mp3',
|
||||
'description':
|
||||
'Rob Pike says, “Simplicity is the art of hiding complexity.” If that’s true, what is simplicity in the context of writing software in Go? Is it even something we should strive for? Can software be too simple? Ian &Kris discuss with return guest sam boyer.',
|
||||
'author': 'Changelog Media',
|
||||
'publishedAt': 1699449300,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
uploadSourceIconSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://itunes.apple.com/lookup?id=1120964487&entity=podcast',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responsePodcastApple, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCall(fetchWithTimeoutSpy, 1, {
|
||||
args: [
|
||||
'https://changelog.com/gotime/feed',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responsePodcastAppleRSS, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCall(uploadSourceIconSpy, 0, {
|
||||
args: [
|
||||
supabaseClient,
|
||||
{
|
||||
'id': 'podcast-myuser-mycolumn-aad37b7b4ebb1f79286d7b9e24bb4163',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'podcast',
|
||||
'title': 'Go Time: Golang, Software Engineering',
|
||||
'options': { 'podcast': 'https://changelog.com/gotime/feed' },
|
||||
'link': 'https://changelog.com/gotime',
|
||||
'icon':
|
||||
'https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357',
|
||||
},
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(
|
||||
'https://cdn.changelog.com/uploads/covers/go-time-original.png?v=63725770357',
|
||||
);
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 2);
|
||||
assertSpyCalls(uploadSourceIconSpy, 1);
|
||||
});
|
||||
@@ -8,8 +8,7 @@ import { unescape } from 'lodash';
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
/**
|
||||
* `isRedditUrl` checks if the provided `url` is a valid Reddit url. A url is
|
||||
@@ -46,11 +45,11 @@ export const getRedditFeed = async (
|
||||
* Get the RSS for the provided `youtube` url and parse it. If a feed doesn't
|
||||
* contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.reddit, {
|
||||
const response = await utils.fetchWithTimeout(source.options.reddit, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'reddit',
|
||||
requestUrl: source.options.reddit,
|
||||
responseStatus: response.status,
|
||||
|
||||
228
supabase/functions/_shared/feed/reddit_test.ts
Normal file
228
supabase/functions/_shared/feed/reddit_test.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import { assertEquals } from 'std/assert';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { getRedditFeed, isRedditUrl } from './reddit.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const responseSubreddit = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
|
||||
<category term="kubernetes" label="r/kubernetes" />
|
||||
<updated>2023-12-10T17:06:31+00:00</updated>
|
||||
<icon>https://www.redditstatic.com/icon.png/</icon>
|
||||
<id>/r/kubernetes.rss</id>
|
||||
<link rel="self" href="https://www.reddit.com/r/kubernetes.rss" type="application/atom+xml" />
|
||||
<link rel="alternate" href="https://www.reddit.com/r/kubernetes" type="text/html" />
|
||||
<subtitle>Kubernetes discussion, news, support, and link sharing.</subtitle>
|
||||
<title>Kubernetes</title>
|
||||
<entry>
|
||||
<author>
|
||||
<name>/u/gctaylor</name>
|
||||
<uri>https://www.reddit.com/user/gctaylor</uri>
|
||||
</author>
|
||||
<category term="kubernetes" label="r/kubernetes" />
|
||||
<content type="html"><!-- SC_OFF --><div class="md"><p>This monthly post can be used to share Kubernetes-related job openings within <strong>your</strong> company. Please include:</p> <ul> <li>Name of the company</li> <li>Location requirements (or lack thereof)</li> <li>At least one of: a link to a job posting/application page or contact details<br/></li> </ul> <p>If you are interested in a job, please contact the poster directly. </p> <p>Common reasons for comment removal:</p> <ul> <li>Not meeting the above requirements</li> <li>Recruiter post / recruiter listings</li> <li>Negative, inflammatory, or abrasive tone</li> </ul> </div><!-- SC_ON --> &#32; submitted by &#32; <a href="https://www.reddit.com/user/gctaylor"> /u/gctaylor </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18895rv/monthly_who_is_hiring/">[link]</a></span> &#32; <span><a href="https://www.reddit.com/r/kubernetes/comments/18895rv/monthly_who_is_hiring/">[Kommentare]</a></span></content>
|
||||
<id>t3_18895rv</id>
|
||||
<link href="https://www.reddit.com/r/kubernetes/comments/18895rv/monthly_who_is_hiring/" />
|
||||
<updated>2023-12-01T11:00:17+00:00</updated>
|
||||
<published>2023-12-01T11:00:17+00:00</published>
|
||||
<title>Monthly: Who is hiring?</title>
|
||||
</entry>
|
||||
<entry>
|
||||
<author>
|
||||
<name>/u/gctaylor</name>
|
||||
<uri>https://www.reddit.com/user/gctaylor</uri>
|
||||
</author>
|
||||
<category term="kubernetes" label="r/kubernetes" />
|
||||
<content type="html"><!-- SC_OFF --><div class="md"><p>Got something working? Figure something out? Make progress that you are excited about? Share here!</p> </div><!-- SC_ON --> &#32; submitted by &#32; <a href="https://www.reddit.com/user/gctaylor"> /u/gctaylor </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18dkeyn/weekly_share_your_victories_thread/">[link]</a></span> &#32; <span><a href="https://www.reddit.com/r/kubernetes/comments/18dkeyn/weekly_share_your_victories_thread/">[Kommentare]</a></span></content>
|
||||
<id>t3_18dkeyn</id>
|
||||
<link href="https://www.reddit.com/r/kubernetes/comments/18dkeyn/weekly_share_your_victories_thread/" />
|
||||
<updated>2023-12-08T11:00:13+00:00</updated>
|
||||
<published>2023-12-08T11:00:13+00:00</published>
|
||||
<title>Weekly: Share your victories thread</title>
|
||||
</entry>
|
||||
<entry>
|
||||
<author>
|
||||
<name>/u/Lanky-Ad4698</name>
|
||||
<uri>https://www.reddit.com/user/Lanky-Ad4698</uri>
|
||||
</author>
|
||||
<category term="kubernetes" label="r/kubernetes" />
|
||||
<content type="html"><!-- SC_OFF --><div class="md"><p>My environment plan:</p> <p>Local: KinD</p> <p>Dev: Hetzner Single VPS</p> <p>Prod: Hetzner Multiple Servers</p> <p>What is the best way to deploy K8s on a single VPS to save money in dev environment? Control plane, worker nodes all on single VPS. The goal is to have the dev environment as similar to Prod (portable), but want to save money on cheap single VPS.</p> <p>I plan on self managing K8s. I know somebody in the comments is just going to be like, just do managed K8s. On that budget mode and want to learn. I really don’t think self managing K8s is that bad and only considered scary because most people just jump straight to managed immediately. I mean I will possibly do managed on PRD, but then Dev and PRD not portable.</p> <p>In terms of stateful Pods, I want dev to have all that in K8s. But PRD most likely will be managed database and session store. Unless having state full things in K8s isn’t that bad. But from what I’ve read nobody likes keeping state full things in K8s. You can kind of see my problem, making dev cheap makes it not as portable to PRD.</p> <p>Yes I’m aware that single VPS K8s is not HA, but that’s not a problem for Dev environment.</p> <p>I see so many tools for self managed K8s, and idk what is the way. Kops? Kubespray?</p> </div><!-- SC_ON --> &#32; submitted by &#32; <a href="https://www.reddit.com/user/Lanky-Ad4698"> /u/Lanky-Ad4698 </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18f3d7o/best_way_to_deploy_k8s_to_single_vps_for_dev/">[link]</a></span> &#32; <span><a href="https://www.reddit.com/r/kubernetes/comments/18f3d7o/best_way_to_deploy_k8s_to_single_vps_for_dev/">[Kommentare]</a></span></content>
|
||||
<id>t3_18f3d7o</id>
|
||||
<link href="https://www.reddit.com/r/kubernetes/comments/18f3d7o/best_way_to_deploy_k8s_to_single_vps_for_dev/" />
|
||||
<updated>2023-12-10T13:13:56+00:00</updated>
|
||||
<published>2023-12-10T13:13:56+00:00</published>
|
||||
<title>Best way to deploy K8s to single VPS for dev environment</title>
|
||||
</entry>
|
||||
<entry>
|
||||
<author>
|
||||
<name>/u/abexami</name>
|
||||
<uri>https://www.reddit.com/user/abexami</uri>
|
||||
</author>
|
||||
<category term="kubernetes" label="r/kubernetes" />
|
||||
<content type="html"><!-- SC_OFF --><div class="md"><p>In our company, we utilize VMWare VSphere as our virtualization solution and a NetApp SAN for storage. The NetApp SAN is connected to VCenter, which is running in FC (Fibre Channel) mode. We have chosen not to support iSCSI or any TCP/IP bound protocol.</p> <p>Currently, we have a production-ready Kubernetes cluster running on nodes from VCenter, which supports our stateless applications. However, we encountered a challenge when we wanted to migrate our stateful workloads (databases, object storages, etc.) to K8S. We are in need of a resilient solution to provide PersistentVolumes in our cluster, and we prefer not to use hostpath. Therefore, we require a CSI plugin that can provide dynamic volumes.</p> <p>After exploring different options, it appears that the NetApp&#39;s CSI plugin (Trident) is not yet production-ready and does not support FC mode. This information is based on the documentation and an issue raised on the Trident GitHub repository.</p> <p>There is a CSI plugin compatible with FC SAN for Dell products, but it seems to be specific to Dell.</p> <p>Overall, we didn&#39;t find a proper way to connect the NetApp SAN directly to K8S and went for another solution and we reached VMWare CNS (Cloud Native Storage) plugin for VSphere, conceptually, it can do the job for us (in conjunction with VSphere CSI and/or VSphere CPI), but it seems that it only support vSAN as storage backend and not the SAN luns (I&#39;m not sure, it was not clear enough in the docs).</p> <p>I have two (or maybe three questions now):</p> <ol> <li>Is there any CSI plugin for NetApp SAN FC mode to directly use it in K8S?</li> <li>Is it possible to connect the CNS directly to SAN and to the VSphere CSI in K8S?</li> <li>If the answer to neither of the above is yes, what can we do to provide K8S storages with our existing hardware?</li> </ol> </div><!-- SC_ON --> &#32; submitted by &#32; <a href="https://www.reddit.com/user/abexami"> /u/abexami </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18f5b8v/how_to_use_netapp_san_storage_to_provide/">[link]</a></span> &#32; <span><a href="https://www.reddit.com/r/kubernetes/comments/18f5b8v/how_to_use_netapp_san_storage_to_provide/">[Kommentare]</a></span></content>
|
||||
<id>t3_18f5b8v</id>
|
||||
<link href="https://www.reddit.com/r/kubernetes/comments/18f5b8v/how_to_use_netapp_san_storage_to_provide/" />
|
||||
<updated>2023-12-10T14:56:01+00:00</updated>
|
||||
<published>2023-12-10T14:56:01+00:00</published>
|
||||
<title>How to use NetApp SAN storage to provide Kubernetes Persistent Volumes?</title>
|
||||
</entry>
|
||||
<entry>
|
||||
<author>
|
||||
<name>/u/ekayan</name>
|
||||
<uri>https://www.reddit.com/user/ekayan</uri>
|
||||
</author>
|
||||
<category term="kubernetes" label="r/kubernetes" />
|
||||
<content type="html"><!-- SC_OFF --><div class="md"><p>Wanted some opinion on CPU limits in the K8s world.</p> <p>I understand CPU is a compressible resource.</p> <p>&#x200B;</p> <p>There are some school of thoughts which advocate NOT setting limits on CPU and let pods overuse when they need.</p> <ul> <li>advocated in - Kubernetes Patterns, 2nd Edition book - <a href="https://learning.oreilly.com/library/view/kubernetes-patterns-2nd/9781098131678/">link</a></li> <li>an year write up here - <a href="https://home.robusta.dev/blog/stop-using-cpu-limits">link</a></li> <li>some old discussion here with POC-- <a href="https://www.reddit.com/r/kubernetes/comments/ulx54i/k8s_without_cpu_limits_we_put_it_on_the_lab_to/">link</a></li> </ul> <p>&#x200B;</p> <p>However, on the documentation, I see that the containers without limits could use all the resouces on the worker node.</p> <p><a href="https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#if-you-do-not-specify-a-cpu-limit">https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#if-you-do-not-specify-a-cpu-limit</a></p> <pre><code>The Container has no upper bound on the CPU resources it can use. The Container could use all of the CPU resources available on the Node where it is running. </code></pre> <p>&#x200B;</p> <p>My question is :</p> <ul> <li>If I don&#39;t set CPU limits, will the containers use all the CPU resources on the worker node ONLY if the worker node has the free/unused resource available?</li> <li>in the extreme scenario if CPU resources are exhausted, will all pods will get proportionally their cut &quot;according to the CPU requests you set.&quot; ?</li> </ul> <p>Just being cautious if other containers in the worker node will suffer if I remove the CPU limits.</p> <p>&#x200B;</p> <p>Thanks much for any thoughts in advance.</p> </div><!-- SC_ON --> &#32; submitted by &#32; <a href="https://www.reddit.com/user/ekayan"> /u/ekayan </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18exirq/request_for_opinion_cpu_limits_in_the_k8s_world/">[link]</a></span> &#32; <span><a href="https://www.reddit.com/r/kubernetes/comments/18exirq/request_for_opinion_cpu_limits_in_the_k8s_world/">[Kommentare]</a></span></content>
|
||||
<id>t3_18exirq</id>
|
||||
<link href="https://www.reddit.com/r/kubernetes/comments/18exirq/request_for_opinion_cpu_limits_in_the_k8s_world/" />
|
||||
<updated>2023-12-10T06:40:08+00:00</updated>
|
||||
<published>2023-12-10T06:40:08+00:00</published>
|
||||
<title>[Request for opinion] : CPU limits in the K8s world</title>
|
||||
</entry>
|
||||
</feed>`;
|
||||
|
||||
Deno.test('isRedditUrl', () => {
|
||||
assertEquals(
|
||||
isRedditUrl('https://www.reddit.com/r/kubernetes/'),
|
||||
true,
|
||||
);
|
||||
assertEquals(isRedditUrl('https://www.google.de/'), false);
|
||||
});
|
||||
|
||||
Deno.test('getRedditFeed', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseSubreddit, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getRedditFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: { reddit: '/r/kubernetes' },
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'reddit',
|
||||
'title': 'Kubernetes',
|
||||
'options': { 'reddit': 'https://www.reddit.com/r/kubernetes.rss' },
|
||||
'link': 'https://www.reddit.com/r/kubernetes.rss',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8-109de6824b3a6446882072dce0d4539d',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8',
|
||||
'title': 'Monthly: Who is hiring?',
|
||||
'link':
|
||||
'https://www.reddit.com/r/kubernetes/comments/18895rv/monthly_who_is_hiring/',
|
||||
'description':
|
||||
'<!-- SC_OFF --><div class="md"><p>This monthly post can be used to share Kubernetes-related job openings within <strong>your</strong> company. Please include:</p> <ul> <li>Name of the company</li> <li>Location requirements (or lack thereof)</li> <li>At least one of: a link to a job posting/application page or contact details<br/></li> </ul> <p>If you are interested in a job, please contact the poster directly. </p> <p>Common reasons for comment removal:</p> <ul> <li>Not meeting the above requirements</li> <li>Recruiter post / recruiter listings</li> <li>Negative, inflammatory, or abrasive tone</li> </ul> </div><!-- SC_ON -->   submitted by   <a href="https://www.reddit.com/user/gctaylor"> /u/gctaylor </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18895rv/monthly_who_is_hiring/">[link]</a></span>   <span><a href="https://www.reddit.com/r/kubernetes/comments/18895rv/monthly_who_is_hiring/">[Kommentare]</a></span>',
|
||||
'author': '/u/gctaylor',
|
||||
'publishedAt': 1701428417,
|
||||
}, {
|
||||
'id':
|
||||
'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8-eb77579ebc7a3ef77471fe91fb4feecc',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8',
|
||||
'title': 'Weekly: Share your victories thread',
|
||||
'link':
|
||||
'https://www.reddit.com/r/kubernetes/comments/18dkeyn/weekly_share_your_victories_thread/',
|
||||
'description':
|
||||
'<!-- SC_OFF --><div class="md"><p>Got something working? Figure something out? Make progress that you are excited about? Share here!</p> </div><!-- SC_ON -->   submitted by   <a href="https://www.reddit.com/user/gctaylor"> /u/gctaylor </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18dkeyn/weekly_share_your_victories_thread/">[link]</a></span>   <span><a href="https://www.reddit.com/r/kubernetes/comments/18dkeyn/weekly_share_your_victories_thread/">[Kommentare]</a></span>',
|
||||
'author': '/u/gctaylor',
|
||||
'publishedAt': 1702033213,
|
||||
}, {
|
||||
'id':
|
||||
'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8-081da9534f66b5a2f9f345747197319d',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8',
|
||||
'title': 'Best way to deploy K8s to single VPS for dev environment',
|
||||
'link':
|
||||
'https://www.reddit.com/r/kubernetes/comments/18f3d7o/best_way_to_deploy_k8s_to_single_vps_for_dev/',
|
||||
'description':
|
||||
'<!-- SC_OFF --><div class="md"><p>My environment plan:</p> <p>Local: KinD</p> <p>Dev: Hetzner Single VPS</p> <p>Prod: Hetzner Multiple Servers</p> <p>What is the best way to deploy K8s on a single VPS to save money in dev environment? Control plane, worker nodes all on single VPS. The goal is to have the dev environment as similar to Prod (portable), but want to save money on cheap single VPS.</p> <p>I plan on self managing K8s. I know somebody in the comments is just going to be like, just do managed K8s. On that budget mode and want to learn. I really don’t think self managing K8s is that bad and only considered scary because most people just jump straight to managed immediately. I mean I will possibly do managed on PRD, but then Dev and PRD not portable.</p> <p>In terms of stateful Pods, I want dev to have all that in K8s. But PRD most likely will be managed database and session store. Unless having state full things in K8s isn’t that bad. But from what I’ve read nobody likes keeping state full things in K8s. You can kind of see my problem, making dev cheap makes it not as portable to PRD.</p> <p>Yes I’m aware that single VPS K8s is not HA, but that’s not a problem for Dev environment.</p> <p>I see so many tools for self managed K8s, and idk what is the way. Kops? Kubespray?</p> </div><!-- SC_ON -->   submitted by   <a href="https://www.reddit.com/user/Lanky-Ad4698"> /u/Lanky-Ad4698 </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18f3d7o/best_way_to_deploy_k8s_to_single_vps_for_dev/">[link]</a></span>   <span><a href="https://www.reddit.com/r/kubernetes/comments/18f3d7o/best_way_to_deploy_k8s_to_single_vps_for_dev/">[Kommentare]</a></span>',
|
||||
'author': '/u/Lanky-Ad4698',
|
||||
'publishedAt': 1702214036,
|
||||
}, {
|
||||
'id':
|
||||
'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8-ea9508517c2f840daf415352c2c2eaf1',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8',
|
||||
'title':
|
||||
'How to use NetApp SAN storage to provide Kubernetes Persistent Volumes?',
|
||||
'link':
|
||||
'https://www.reddit.com/r/kubernetes/comments/18f5b8v/how_to_use_netapp_san_storage_to_provide/',
|
||||
'description':
|
||||
'<!-- SC_OFF --><div class="md"><p>In our company, we utilize VMWare VSphere as our virtualization solution and a NetApp SAN for storage. The NetApp SAN is connected to VCenter, which is running in FC (Fibre Channel) mode. We have chosen not to support iSCSI or any TCP/IP bound protocol.</p> <p>Currently, we have a production-ready Kubernetes cluster running on nodes from VCenter, which supports our stateless applications. However, we encountered a challenge when we wanted to migrate our stateful workloads (databases, object storages, etc.) to K8S. We are in need of a resilient solution to provide PersistentVolumes in our cluster, and we prefer not to use hostpath. Therefore, we require a CSI plugin that can provide dynamic volumes.</p> <p>After exploring different options, it appears that the NetApp\'s CSI plugin (Trident) is not yet production-ready and does not support FC mode. This information is based on the documentation and an issue raised on the Trident GitHub repository.</p> <p>There is a CSI plugin compatible with FC SAN for Dell products, but it seems to be specific to Dell.</p> <p>Overall, we didn\'t find a proper way to connect the NetApp SAN directly to K8S and went for another solution and we reached VMWare CNS (Cloud Native Storage) plugin for VSphere, conceptually, it can do the job for us (in conjunction with VSphere CSI and/or VSphere CPI), but it seems that it only support vSAN as storage backend and not the SAN luns (I\'m not sure, it was not clear enough in the docs).</p> <p>I have two (or maybe three questions now):</p> <ol> <li>Is there any CSI plugin for NetApp SAN FC mode to directly use it in K8S?</li> <li>Is it possible to connect the CNS directly to SAN and to the VSphere CSI in K8S?</li> <li>If the answer to neither of the above is yes, what can we do to provide K8S storages with our existing hardware?</li> </ol> </div><!-- SC_ON -->   submitted by   <a href="https://www.reddit.com/user/abexami"> /u/abexami </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18f5b8v/how_to_use_netapp_san_storage_to_provide/">[link]</a></span>   <span><a href="https://www.reddit.com/r/kubernetes/comments/18f5b8v/how_to_use_netapp_san_storage_to_provide/">[Kommentare]</a></span>',
|
||||
'author': '/u/abexami',
|
||||
'publishedAt': 1702220161,
|
||||
}, {
|
||||
'id':
|
||||
'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8-a0c951151b5cfea0d6f9281c791ade02',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'reddit-myuser-mycolumn-87e62a33042b3fdf4eac36ae57d55fc8',
|
||||
'title': '[Request for opinion] : CPU limits in the K8s world',
|
||||
'link':
|
||||
'https://www.reddit.com/r/kubernetes/comments/18exirq/request_for_opinion_cpu_limits_in_the_k8s_world/',
|
||||
'description':
|
||||
'<!-- SC_OFF --><div class="md"><p>Wanted some opinion on CPU limits in the K8s world.</p> <p>I understand CPU is a compressible resource.</p> <p>​</p> <p>There are some school of thoughts which advocate NOT setting limits on CPU and let pods overuse when they need.</p> <ul> <li>advocated in - Kubernetes Patterns, 2nd Edition book - <a href="https://learning.oreilly.com/library/view/kubernetes-patterns-2nd/9781098131678/">link</a></li> <li>an year write up here - <a href="https://home.robusta.dev/blog/stop-using-cpu-limits">link</a></li> <li>some old discussion here with POC-- <a href="https://www.reddit.com/r/kubernetes/comments/ulx54i/k8s_without_cpu_limits_we_put_it_on_the_lab_to/">link</a></li> </ul> <p>​</p> <p>However, on the documentation, I see that the containers without limits could use all the resouces on the worker node.</p> <p><a href="https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#if-you-do-not-specify-a-cpu-limit">https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#if-you-do-not-specify-a-cpu-limit</a></p> <pre><code>The Container has no upper bound on the CPU resources it can use. The Container could use all of the CPU resources available on the Node where it is running. </code></pre> <p>​</p> <p>My question is :</p> <ul> <li>If I don\'t set CPU limits, will the containers use all the CPU resources on the worker node ONLY if the worker node has the free/unused resource available?</li> <li>in the extreme scenario if CPU resources are exhausted, will all pods will get proportionally their cut "according to the CPU requests you set." ?</li> </ul> <p>Just being cautious if other containers in the worker node will suffer if I remove the CPU limits.</p> <p>​</p> <p>Thanks much for any thoughts in advance.</p> </div><!-- SC_ON -->   submitted by   <a href="https://www.reddit.com/user/ekayan"> /u/ekayan </a> <br/> <span><a href="https://www.reddit.com/r/kubernetes/comments/18exirq/request_for_opinion_cpu_limits_in_the_k8s_world/">[link]</a></span>   <span><a href="https://www.reddit.com/r/kubernetes/comments/18exirq/request_for_opinion_cpu_limits_in_the_k8s_world/">[Kommentare]</a></span>',
|
||||
'author': '/u/ekayan',
|
||||
'publishedAt': 1702190408,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://www.reddit.com/r/kubernetes.rss',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseSubreddit, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
@@ -8,11 +8,9 @@ import * as cheerio from 'cheerio';
|
||||
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { getFavicon } from './utils/getFavicon.ts';
|
||||
import { uploadSourceIcon } from './utils/uploadFile.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
export const getRSSFeed = async (
|
||||
supabaseClient: SupabaseClient,
|
||||
@@ -31,7 +29,7 @@ export const getRSSFeed = async (
|
||||
|
||||
let feed = await getFeed(source);
|
||||
if (!feed) {
|
||||
log(
|
||||
utils.log(
|
||||
'debug',
|
||||
'Failed to get RSS feed, try to get RSS feed from website',
|
||||
{ requestUrl: source.options.rss },
|
||||
@@ -86,7 +84,7 @@ export const getRSSFeed = async (
|
||||
*/
|
||||
if (!source.icon) {
|
||||
if (source.link) {
|
||||
const favicon = await getFavicon(source.link);
|
||||
const favicon = await feedutils.getFavicon(source.link);
|
||||
if (favicon && favicon.url.startsWith('https://')) {
|
||||
source.icon = favicon.url;
|
||||
}
|
||||
@@ -100,7 +98,7 @@ export const getRSSFeed = async (
|
||||
}
|
||||
}
|
||||
|
||||
source.icon = await uploadSourceIcon(supabaseClient, source);
|
||||
source.icon = await feedutils.uploadSourceIcon(supabaseClient, source);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,6 +142,8 @@ export const getRSSFeed = async (
|
||||
? Math.floor(entry.published.getTime() / 1000)
|
||||
: entry.updated
|
||||
? Math.floor(entry.updated.getTime() / 1000)
|
||||
: entry['dc:date']
|
||||
? getDCDateTimestamp(entry['dc:date'])
|
||||
: Math.floor(new Date().getTime() / 1000),
|
||||
});
|
||||
}
|
||||
@@ -158,13 +158,13 @@ export const getRSSFeed = async (
|
||||
*/
|
||||
const getFeed = async (source: ISource): Promise<Feed | undefined> => {
|
||||
try {
|
||||
const response = await fetchWithTimeout(
|
||||
const response = await utils.fetchWithTimeout(
|
||||
source.options!.rss!,
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'rss',
|
||||
requestUrl: source.options!.rss!,
|
||||
responseStatus: response.status,
|
||||
@@ -194,7 +194,7 @@ const getFeedFromWebsite = async (
|
||||
source: ISource,
|
||||
): Promise<Feed | undefined> => {
|
||||
try {
|
||||
const response = await fetchWithTimeout(
|
||||
const response = await utils.fetchWithTimeout(
|
||||
source.options!.rss!,
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
@@ -236,7 +236,7 @@ const skipEntry = (
|
||||
if (
|
||||
!entry.title?.value ||
|
||||
(entry.links.length === 0 || !entry.links[0].href) ||
|
||||
(!entry.published && !entry.updated)
|
||||
(!entry.published && !entry.updated && !entry['dc:date'])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -251,11 +251,29 @@ const skipEntry = (
|
||||
Math.floor(entry.updated.getTime() / 1000) <= (sourceUpdatedAt - 10)
|
||||
) {
|
||||
return true;
|
||||
} else if (
|
||||
entry['dc:date'] &&
|
||||
getDCDateTimestamp(entry['dc:date']) <= (sourceUpdatedAt - 10)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* `getDCDateTimestamp` is a helper function to get the timestamp of a `dc:date`
|
||||
* tag. The `dc:date` tag can either be a `Date` object or an object with a
|
||||
* `value` property which is a `Date` object.
|
||||
*/
|
||||
const getDCDateTimestamp = (dcdate: Date | { value: Date }): number => {
|
||||
if (dcdate instanceof Date) {
|
||||
return Math.floor(dcdate.getTime() / 1000);
|
||||
} else {
|
||||
return Math.floor(dcdate.value.getTime() / 1000);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* `generateSourceId` generates a unique source id based on the user id, column
|
||||
* id and the link of the RSS feed. We use the MD5 algorithm for the link to
|
||||
|
||||
1370
supabase/functions/_shared/feed/rss_test.ts
Normal file
1370
supabase/functions/_shared/feed/rss_test.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,7 @@ import { unescape } from 'lodash';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
export const getStackoverflowFeed = async (
|
||||
_supabaseClient: SupabaseClient,
|
||||
@@ -34,11 +33,15 @@ export const getStackoverflowFeed = async (
|
||||
* Get the RSS for the provided `stackoverflow` url and parse it. If a feed
|
||||
* doesn't contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.stackoverflow.url, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const response = await utils.fetchWithTimeout(
|
||||
source.options.stackoverflow.url,
|
||||
{
|
||||
method: 'get',
|
||||
},
|
||||
5000,
|
||||
);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'stackoverflow',
|
||||
requestUrl: source.options.stackoverflow.url,
|
||||
responseStatus: response.status,
|
||||
|
||||
203
supabase/functions/_shared/feed/stackoverflow_test.ts
Normal file
203
supabase/functions/_shared/feed/stackoverflow_test.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { getStackoverflowFeed } from './stackoverflow.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const responseTag = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" xmlns:re="http://purl.org/atompub/rank/1.0">
|
||||
<title type="text">Newest questions tagged kubernetes - Stack Overflow</title>
|
||||
<link rel="self" href="https://stackoverflow.com/feeds/tag?tagnames=kubernetes&sort=newest" type="application/atom+xml" />
|
||||
<link rel="alternate" href="https://stackoverflow.com/questions/tagged/?tagnames=kubernetes&sort=newest" type="text/html" />
|
||||
<subtitle>most recent 30 from stackoverflow.com</subtitle>
|
||||
<updated>2023-12-10T17:21:25Z</updated>
|
||||
<id>https://stackoverflow.com/feeds/tag?tagnames=kubernetes&sort=newest</id>
|
||||
<creativeCommons:license>https://creativecommons.org/licenses/by-sa/4.0/rdf</creativeCommons:license>
|
||||
<entry>
|
||||
<id>https://stackoverflow.com/q/77635559</id>
|
||||
<re:rank scheme="https://stackoverflow.com">0</re:rank>
|
||||
<title type="text">Kubernetes Ingress behind Cloud Load Balancer</title>
|
||||
<category scheme="https://stackoverflow.com/tags" term="kubernetes" />
|
||||
<category scheme="https://stackoverflow.com/tags" term="kubernetes-ingress" />
|
||||
<category scheme="https://stackoverflow.com/tags" term="ingress-controller" />
|
||||
<category scheme="https://stackoverflow.com/tags" term="hetzner-cloud" />
|
||||
<author>
|
||||
<name>Dominik.W</name>
|
||||
<uri>https://stackoverflow.com/users/23076575</uri>
|
||||
</author>
|
||||
<link rel="alternate" href="https://stackoverflow.com/questions/77635559/kubernetes-ingress-behind-cloud-load-balancer" />
|
||||
<published>2023-12-10T16:39:44Z</published>
|
||||
<updated>2023-12-10T16:39:44Z</updated>
|
||||
<summary type="html"><p>When using an Ingress Controller in Kubernetes the Ingress service is usually exposed via Load Balancer. Now I’m trining to understand on how this exactly works.
|
||||
As I understand it the Ingress Controller is just running as an Pod like any other app and gets exposed via the Load Balancer.
|
||||
When configuring the external load balancer what target do I set, the Worker nodes or the master nodes, or does this even matter because I use a Service and then it’s automatically internally Load balanced?</p>
|
||||
<p>I try to get this Right so I can setup a Kubernetes Cluster in the Hetzner Cloud, because it has no managed service I need to do basically everything on my on but it provides the services to theoretically host a full HA cluster.
|
||||
So the plan is to have for the beginning 3 Master Nodes and 2/3 Worker Nodes and an Managed Load Balancer in front of everything.
|
||||
I thought about having 2 Cloud Networks one lb-network for the master nodes and the load balancer and a second one cluster network for the master and worker nodes. But with that approach every incoming traffic needs to get through the Masters to get terminated at the Ingress Controller which is running on the Worker, I like that approach because it allows me to use fewer targets on the Load Balancer to save some money also I could mostly isolate the workers from incoming traffic on a network level. Is that approach possible and even best practices or what do you recommend?</p></summary>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>https://stackoverflow.com/q/77635466</id>
|
||||
<re:rank scheme="https://stackoverflow.com">0</re:rank>
|
||||
<title type="text">Application (valhalla-server) not running (or accessible) )when exposed through clusterIP or NodePort service</title>
|
||||
<category scheme="https://stackoverflow.com/tags" term="kubernetes" />
|
||||
<category scheme="https://stackoverflow.com/tags" term="amazon-eks" />
|
||||
<category scheme="https://stackoverflow.com/tags" term="project-valhalla" />
|
||||
<author>
|
||||
<name>kubexplore</name>
|
||||
<uri>https://stackoverflow.com/users/23076494</uri>
|
||||
</author>
|
||||
<link rel="alternate" href="https://stackoverflow.com/questions/77635466/application-valhalla-server-not-running-or-accessible-when-exposed-through" />
|
||||
<published>2023-12-10T16:07:16Z</published>
|
||||
<updated>2023-12-10T16:33:29Z</updated>
|
||||
<summary type="html"><p>I have created a pod to deploy valhalla-server, yaml below:</p>
|
||||
<pre><code>apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: valahalla-pod
|
||||
labels:
|
||||
app: valahalla-app-pod # Updated label name
|
||||
spec:
|
||||
containers:
|
||||
- name: docker-valahalla
|
||||
image: ghcr.io/gis-ops/docker-valhalla/valhalla
|
||||
env:
|
||||
- name: tile_urls
|
||||
value: &quot;https://download.geofabrik.de/europe/andorra-latest.osm.pbf ghcr.io/gis-ops/docker-valhalla/valhalla:latest&quot;
|
||||
ports:
|
||||
- containerPort: 8002
|
||||
volumeMounts:
|
||||
- name: my-local-folder
|
||||
mountPath: /custom_files
|
||||
volumes:
|
||||
- name: my-local-folder
|
||||
hostPath:
|
||||
path: /home/ubuntu/custom_files
|
||||
</code></pre>
|
||||
<p>When I exec into this pod, I am able to access the valhalla server on localhost on port 8002. But I have created a service for the pod, yaml below:</p>
|
||||
<pre><code>apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: valahalla-service
|
||||
spec:
|
||||
selector:
|
||||
app: valahalla-app-pod
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80 # Port exposed by the service
|
||||
targetPort: 8002
|
||||
type: NodePort
|
||||
</code></pre>
|
||||
<p>I am not able to access my application through this! It gives output when I curl by going into the pod but not when I try to access it from outisde.</p>
|
||||
<p>I am using EKS on AWS for this.</p>
|
||||
<p>I was using deployment before, I've tried using pod instead and changed service from clusterIP to NodePort.</p></summary>
|
||||
</entry>
|
||||
</feed>`;
|
||||
|
||||
Deno.test('getStackoverflowFeed', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(responseTag, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getStackoverflowFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: {
|
||||
stackoverflow: { type: 'tag', tag: 'kubernetes', sort: 'newest' },
|
||||
},
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'stackoverflow-myuser-mycolumn-b33aefc859cbc9c75f22dc8de83b59e7',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'stackoverflow',
|
||||
'title': 'Newest questions tagged kubernetes - Stack Overflow',
|
||||
'options': {
|
||||
'stackoverflow': {
|
||||
'type': 'tag',
|
||||
'tag': 'kubernetes',
|
||||
'sort': 'newest',
|
||||
'url':
|
||||
'https://stackoverflow.com/feeds/tag?tagnames=kubernetes&sort=newest',
|
||||
},
|
||||
},
|
||||
'link':
|
||||
'https://stackoverflow.com/feeds/tag?tagnames=kubernetes&sort=newest',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'stackoverflow-myuser-mycolumn-b33aefc859cbc9c75f22dc8de83b59e7-4ccc40394df08fa6092fa370ad44fa79',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId':
|
||||
'stackoverflow-myuser-mycolumn-b33aefc859cbc9c75f22dc8de83b59e7',
|
||||
'title': 'Kubernetes Ingress behind Cloud Load Balancer',
|
||||
'link':
|
||||
'https://stackoverflow.com/questions/77635559/kubernetes-ingress-behind-cloud-load-balancer',
|
||||
'description':
|
||||
'<p>When using an Ingress Controller in Kubernetes the Ingress service is usually exposed via Load Balancer. Now I’m trining to understand on how this exactly works.\nAs I understand it the Ingress Controller is just running as an Pod like any other app and gets exposed via the Load Balancer.\nWhen configuring the external load balancer what target do I set, the Worker nodes or the master nodes, or does this even matter because I use a Service and then it’s automatically internally Load balanced?</p>\n<p>I try to get this Right so I can setup a Kubernetes Cluster in the Hetzner Cloud, because it has no managed service I need to do basically everything on my on but it provides the services to theoretically host a full HA cluster.\nSo the plan is to have for the beginning 3 Master Nodes and 2/3 Worker Nodes and an Managed Load Balancer in front of everything.\nI thought about having 2 Cloud Networks one lb-network for the master nodes and the load balancer and a second one cluster network for the master and worker nodes. But with that approach every incoming traffic needs to get through the Masters to get terminated at the Ingress Controller which is running on the Worker, I like that approach because it allows me to use fewer targets on the Load Balancer to save some money also I could mostly isolate the workers from incoming traffic on a network level. Is that approach possible and even best practices or what do you recommend?</p>',
|
||||
'publishedAt': 1702226384,
|
||||
}, {
|
||||
'id':
|
||||
'stackoverflow-myuser-mycolumn-b33aefc859cbc9c75f22dc8de83b59e7-6f932d7a7105a5e38fa70de9bbad6fe5',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId':
|
||||
'stackoverflow-myuser-mycolumn-b33aefc859cbc9c75f22dc8de83b59e7',
|
||||
'title':
|
||||
'Application (valhalla-server) not running (or accessible) )when exposed through clusterIP or NodePort service',
|
||||
'link':
|
||||
'https://stackoverflow.com/questions/77635466/application-valhalla-server-not-running-or-accessible-when-exposed-through',
|
||||
'description':
|
||||
'<p>I have created a pod to deploy valhalla-server, yaml below:</p>\n<pre><code>apiVersion: v1\nkind: Pod\nmetadata:\n name: valahalla-pod\n labels:\n app: valahalla-app-pod # Updated label name\nspec:\n containers:\n - name: docker-valahalla\n image: ghcr.io/gis-ops/docker-valhalla/valhalla\n env:\n - name: tile_urls\n value: "https://download.geofabrik.de/europe/andorra-latest.osm.pbf ghcr.io/gis-ops/docker-valhalla/valhalla:latest"\n ports:\n - containerPort: 8002\n volumeMounts:\n - name: my-local-folder\n mountPath: /custom_files\n volumes:\n - name: my-local-folder\n hostPath:\n path: /home/ubuntu/custom_files\n</code></pre>\n<p>When I exec into this pod, I am able to access the valhalla server on localhost on port 8002. But I have created a service for the pod, yaml below:</p>\n<pre><code>apiVersion: v1\nkind: Service\nmetadata:\n name: valahalla-service\nspec:\n selector:\n app: valahalla-app-pod\n ports:\n - protocol: TCP\n port: 80 # Port exposed by the service\n targetPort: 8002\n type: NodePort\n</code></pre>\n<p>I am not able to access my application through this! It gives output when I curl by going into the pod but not when I try to access it from outisde.</p>\n<p>I am using EKS on AWS for this.</p>\n<p>I was using deployment before, I\'ve tried using pod instead and changed service from clusterIP to NodePort.</p>',
|
||||
'publishedAt': 1702224436,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://stackoverflow.com/feeds/tag?tagnames=kubernetes&sort=newest',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(responseTag, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
@@ -8,8 +8,7 @@ import { Redis } from 'redis';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
/**
|
||||
* `isTumblrUrl` checks if the provided `url` is a valid Tumblr url. A url is
|
||||
@@ -50,11 +49,11 @@ export const getTumblrFeed = async (
|
||||
* Get the RSS for the provided `tumblr` url and parse it. If a feed doesn't
|
||||
* contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.tumblr, {
|
||||
const response = await utils.fetchWithTimeout(source.options.tumblr, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'tumblr',
|
||||
requestUrl: source.options.tumblr,
|
||||
responseStatus: response.status,
|
||||
|
||||
178
supabase/functions/_shared/feed/tumblr_test.ts
Normal file
178
supabase/functions/_shared/feed/tumblr_test.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { assertEquals } from 'std/assert';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
assertSpyCall,
|
||||
assertSpyCalls,
|
||||
returnsNext,
|
||||
stub,
|
||||
} from 'std/testing/mock';
|
||||
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { getTumblrFeed, isTumblrUrl } from './tumblr.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
|
||||
const supabaseClient = createClient('http://localhost:54321', 'test123');
|
||||
const mockProfile: IProfile = {
|
||||
id: '',
|
||||
tier: 'free',
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
};
|
||||
const mockSource: ISource = {
|
||||
id: '',
|
||||
columnId: 'mycolumn',
|
||||
userId: 'myuser',
|
||||
type: 'medium',
|
||||
title: '',
|
||||
};
|
||||
|
||||
const response = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
||||
<channel>
|
||||
<description />
|
||||
<title>Today on Tumblr</title>
|
||||
<generator>Tumblr (3.0; @todayontumblr)</generator>
|
||||
<link>https://todayontumblr.tumblr.com/</link>
|
||||
<item>
|
||||
<title>NEIL WON BEST ACTOR AT THE GAME AWARDS!!!!!!</title>
|
||||
<description><p ><a class="tumblr_blog" href="https://argetcross.tumblr.com/post/736098070263218176/neil-won-best-actor-at-the-game-awards">argetcross </a >:</p ><blockquote ><p >NEIL WON BEST ACTOR AT THE GAME AWARDS!!!!!!</p ><div class="npf_row"><figure class="tmblr-full" data-orig-height="945" data-orig-width="2048"><img src="https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s640x960/b8e88192aa512164126093c75681143fe851fead.jpg" data-orig-height="945" data-orig-width="2048" srcset="https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s75x75_c1/eff71b743942cc3094990943933886d7d289ae2f.jpg 75w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s100x200/f0d8dc8bb82087bfd457f9fcc92e8ec0a2d79250.jpg 100w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s250x400/fa2684d74d1a1321c7bece3f6bd0caa40392c36d.jpg 250w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s400x600/b578a131611c62237a1403a081ae8dbf8bb57b72.jpg 400w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s500x750/bed251d1aef5fffcc65ea69f8a113d0a99585081.jpg 500w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s540x810/3daeb676fcf06741f47af8f7b910b8e8368622e1.jpg 540w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s640x960/b8e88192aa512164126093c75681143fe851fead.jpg 640w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s1280x1920/2da89407179aa94cd58b581a58944fc2160f9d56.jpg 1280w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s2048x3072/49b9d11b154993cad04a1e2c61071573e184002e.jpg 2048w" sizes="(max-width: 1280px) 100vw, 1280px"/></figure ></div ></blockquote ></description>
|
||||
<link>https://www.tumblr.com/todayontumblr/736339394445918208</link>
|
||||
<guid>https://www.tumblr.com/todayontumblr/736339394445918208</guid>
|
||||
<pubDate>Sun, 10 Dec 2023 12:06:08 -0500</pubDate>
|
||||
<category>today on tumblr</category>
|
||||
</item>
|
||||
<item>
|
||||
<title>That &rsquo;s why they were rushing everyone &rsquo;s speeches cause Geoff wanted enough time to talk to his wife &hellip;</title>
|
||||
<description><p ><a class="tumblr_blog" href="https://orallech.tumblr.com/post/736101836478611456/thats-why-they-were-rushing-everyones-speeches">orallech </a >:</p ><blockquote ><p >That’s why they were rushing everyone’s speeches cause Geoff wanted enough time to talk to his wife kojima </p ></blockquote ></description>
|
||||
<link>https://www.tumblr.com/todayontumblr/736335427403907072</link>
|
||||
<guid>https://www.tumblr.com/todayontumblr/736335427403907072</guid>
|
||||
<pubDate>Sun, 10 Dec 2023 11:03:05 -0500</pubDate>
|
||||
<category>today on tumblr</category>
|
||||
</item>
|
||||
<item>
|
||||
<title>Muppet Fact #925</title>
|
||||
<description><p ><a class="tumblr_blog" href="https://muppet-facts.tumblr.com/post/736100473317343232/muppet-fact-925">muppet-facts </a >:</p ><blockquote ><p >At the 2023 Game Awards, Gonzo presented the award for Best Debut Indie Game with Geoff Keighley. He said he &rsquo;s been playing <i >Tears of the Kingdom </i >. He lost days following a Cucco up the hill. He also has a conspiracy that a lot of games released this year follow a chicken theme. </p ><div class="npf_row"><figure class="tmblr-full" data-orig-height="2160" data-orig-width="3840"><img src="https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s640x960/de1345504db6ac2e012d6aef542489fbfd8a522b.png" data-orig-height="2160" data-orig-width="3840" srcset="https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s75x75_c1/24353b4aa077be11678ff83f73bcb2cdc02a0a82.png 75w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s100x200/f8bae9a792c7644dee26379f44fb8d36f476213d.png 100w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s250x400/f7d6d9833510950930a3e14e3bc345d067a30868.png 250w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s400x600/0a16568c844f59cf493607a1cd6a32899ca5cf47.png 400w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s500x750/c4635e448d70ab8200d5f29256a7153301a8e415.png 500w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s540x810/d1f17ba75dd935012801d629591ee480ce8eca49.png 540w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s640x960/de1345504db6ac2e012d6aef542489fbfd8a522b.png 640w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s1280x1920/f7849216be34e6744bcf83e8d06e306ef486d439.png 1280w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s2048x3072/673081b3df788fbe38d4237b236839f59ba339b8.png 2048w" sizes="(max-width: 1280px) 100vw, 1280px"/></figure ><figure class="tmblr-full" data-orig-height="1080" data-orig-width="1920"><img src="https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s640x960/f0dfd4aa4229944da9b0e525e6c9a0ec89143373.png" data-orig-height="1080" data-orig-width="1920" srcset="https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s75x75_c1/0268965777520275909841726829a2571b4dd4b1.png 75w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s100x200/e3b79f463b947c8857dddceda099e4cfe6e53e9c.png 100w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s250x400/4eeb69a20d2872719cf1e2e28b54367d28a33114.png 250w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s400x600/445c4b2e690ae3075b362a105af9b4aa45adee88.png 400w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s500x750/30a34daebebee02fc501e9ca7361d6f3221ed856.png 500w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s540x810/29f17a07b007f7476d4a321a56a29f333c32b93e.png 540w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s640x960/f0dfd4aa4229944da9b0e525e6c9a0ec89143373.png 640w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s1280x1920/6b40fc10a77481213526f9f4412aca16bc0ce173.png 1280w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s2048x3072/286ccace479893c0e6a7803080132f3b7869b634.png 1920w" sizes="(max-width: 1280px) 100vw, 1280px"/></figure ></div ><p ><b >Source: </b ></p ><p >The Game Awards 2023. December 7, 2023. </p ></blockquote ></description>
|
||||
<link>https://www.tumblr.com/todayontumblr/736331601085120512</link>
|
||||
<guid>https://www.tumblr.com/todayontumblr/736331601085120512</guid>
|
||||
<pubDate>Sun, 10 Dec 2023 10:02:16 -0500</pubDate>
|
||||
<category>today on tumblr</category>
|
||||
</item>
|
||||
<item>
|
||||
<title>Hardest image in gaming culture</title>
|
||||
<description><p ><a class="tumblr_blog" href="https://www.tumblr.com/temperedknight/736102767863742464/hardest-image-in-gaming-culture">temperedknight </a >:</p ><blockquote ><div class="npf_row"><figure class="tmblr-full" data-orig-height="521" data-orig-width="1063"><img src="https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s640x960/8ce0d3c4fa8efea0a196cb1921aa64cc43950f8d.png" data-orig-height="521" data-orig-width="1063" srcset="https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s75x75_c1/c86df7bd8018d29a3848bf9ebf68c645f3eb43ff.png 75w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s100x200/4d9c7c121ef92a8ba19983b2a5a56e5406a03e0c.png 100w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s250x400/2cf0925fa1dfd3fae5a991e88453f024be044f70.png 250w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s400x600/88f6685b3a8f0bafeb83ccb875d82d29269b268d.png 400w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s500x750/e084806a49fb3808cd10d8916bcaec53493fc181.png 500w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s540x810/4630feda50c2ec3c41a7d7413cae5f853cdb5034.png 540w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s640x960/8ce0d3c4fa8efea0a196cb1921aa64cc43950f8d.png 640w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s1280x1920/c48e8fb2dad3ff167d0a318025c6edfff19a49fc.png 1063w" sizes="(max-width: 1063px) 100vw, 1063px"/></figure ></div ><p >Hardest image in gaming culture </p ></blockquote ></description>
|
||||
<link>https://www.tumblr.com/todayontumblr/736327882138386432</link>
|
||||
<guid>https://www.tumblr.com/todayontumblr/736327882138386432</guid>
|
||||
<pubDate>Sun, 10 Dec 2023 09:03:09 -0500</pubDate>
|
||||
<category>today on tumblr</category>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
Deno.test('isTumblrUrl', () => {
|
||||
assertEquals(
|
||||
isTumblrUrl('https://www.tumblr.com/todayontumblr'),
|
||||
true,
|
||||
);
|
||||
assertEquals(isTumblrUrl('https://www.google.de/'), false);
|
||||
});
|
||||
|
||||
Deno.test('getTumblrFeed', async () => {
|
||||
const fetchWithTimeoutSpy = stub(
|
||||
utils,
|
||||
'fetchWithTimeout',
|
||||
returnsNext([
|
||||
new Promise((resolve) => {
|
||||
resolve(new Response(response, { status: 200 }));
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
try {
|
||||
const { source, items } = await getTumblrFeed(
|
||||
supabaseClient,
|
||||
undefined,
|
||||
mockProfile,
|
||||
{
|
||||
...mockSource,
|
||||
options: { tumblr: 'https://www.tumblr.com/todayontumblr' },
|
||||
},
|
||||
);
|
||||
feedutils.assertEqualsSource(source, {
|
||||
'id': 'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c',
|
||||
'columnId': 'mycolumn',
|
||||
'userId': 'myuser',
|
||||
'type': 'tumblr',
|
||||
'title': 'Today on Tumblr',
|
||||
'options': { 'tumblr': 'https://todayontumblr.tumblr.com/rss' },
|
||||
'link': 'https://todayontumblr.tumblr.com/',
|
||||
});
|
||||
feedutils.assertEqualsItems(items, [{
|
||||
'id':
|
||||
'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c-cadfdb150b18480df6d781f845d0fec5',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c',
|
||||
'title': 'NEIL WON BEST ACTOR AT THE GAME AWARDS!!!!!!',
|
||||
'link': 'https://www.tumblr.com/todayontumblr/736339394445918208',
|
||||
'media':
|
||||
'https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s640x960/b8e88192aa512164126093c75681143fe851fead.jpg',
|
||||
'description':
|
||||
'<p ><a class="tumblr_blog" href="https://argetcross.tumblr.com/post/736098070263218176/neil-won-best-actor-at-the-game-awards">argetcross </a >:</p ><blockquote ><p >NEIL WON BEST ACTOR AT THE GAME AWARDS!!!!!!</p ><div class="npf_row"><figure class="tmblr-full" data-orig-height="945" data-orig-width="2048"><img src="https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s640x960/b8e88192aa512164126093c75681143fe851fead.jpg" data-orig-height="945" data-orig-width="2048" srcset="https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s75x75_c1/eff71b743942cc3094990943933886d7d289ae2f.jpg 75w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s100x200/f0d8dc8bb82087bfd457f9fcc92e8ec0a2d79250.jpg 100w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s250x400/fa2684d74d1a1321c7bece3f6bd0caa40392c36d.jpg 250w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s400x600/b578a131611c62237a1403a081ae8dbf8bb57b72.jpg 400w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s500x750/bed251d1aef5fffcc65ea69f8a113d0a99585081.jpg 500w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s540x810/3daeb676fcf06741f47af8f7b910b8e8368622e1.jpg 540w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s640x960/b8e88192aa512164126093c75681143fe851fead.jpg 640w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s1280x1920/2da89407179aa94cd58b581a58944fc2160f9d56.jpg 1280w, https://64.media.tumblr.com/3721ad004e334d62e290e5062a296566/a5567169c27bf241-f9/s2048x3072/49b9d11b154993cad04a1e2c61071573e184002e.jpg 2048w" sizes="(max-width: 1280px) 100vw, 1280px"/></figure ></div ></blockquote >',
|
||||
'publishedAt': 1702227968,
|
||||
}, {
|
||||
'id':
|
||||
'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c-9cab913d4df84b6a40d7f90de40e6b24',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c',
|
||||
'title':
|
||||
'That ’s why they were rushing everyone ’s speeches cause Geoff wanted enough time to talk to his wife …',
|
||||
'link': 'https://www.tumblr.com/todayontumblr/736335427403907072',
|
||||
'description':
|
||||
'<p ><a class="tumblr_blog" href="https://orallech.tumblr.com/post/736101836478611456/thats-why-they-were-rushing-everyones-speeches">orallech </a >:</p ><blockquote ><p >That’s why they were rushing everyone’s speeches cause Geoff wanted enough time to talk to his wife kojima </p ></blockquote >',
|
||||
'publishedAt': 1702224185,
|
||||
}, {
|
||||
'id':
|
||||
'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c-8c7e748f46996222cfbf724b7748be62',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c',
|
||||
'title': 'Muppet Fact #925',
|
||||
'link': 'https://www.tumblr.com/todayontumblr/736331601085120512',
|
||||
'media':
|
||||
'https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s640x960/de1345504db6ac2e012d6aef542489fbfd8a522b.png',
|
||||
'description':
|
||||
'<p ><a class="tumblr_blog" href="https://muppet-facts.tumblr.com/post/736100473317343232/muppet-fact-925">muppet-facts </a >:</p ><blockquote ><p >At the 2023 Game Awards, Gonzo presented the award for Best Debut Indie Game with Geoff Keighley. He said he ’s been playing <i >Tears of the Kingdom </i >. He lost days following a Cucco up the hill. He also has a conspiracy that a lot of games released this year follow a chicken theme. </p ><div class="npf_row"><figure class="tmblr-full" data-orig-height="2160" data-orig-width="3840"><img src="https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s640x960/de1345504db6ac2e012d6aef542489fbfd8a522b.png" data-orig-height="2160" data-orig-width="3840" srcset="https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s75x75_c1/24353b4aa077be11678ff83f73bcb2cdc02a0a82.png 75w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s100x200/f8bae9a792c7644dee26379f44fb8d36f476213d.png 100w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s250x400/f7d6d9833510950930a3e14e3bc345d067a30868.png 250w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s400x600/0a16568c844f59cf493607a1cd6a32899ca5cf47.png 400w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s500x750/c4635e448d70ab8200d5f29256a7153301a8e415.png 500w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s540x810/d1f17ba75dd935012801d629591ee480ce8eca49.png 540w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s640x960/de1345504db6ac2e012d6aef542489fbfd8a522b.png 640w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s1280x1920/f7849216be34e6744bcf83e8d06e306ef486d439.png 1280w, https://64.media.tumblr.com/7338226115b70192b7b37dae667dc0b3/217c86a94d5179ce-1e/s2048x3072/673081b3df788fbe38d4237b236839f59ba339b8.png 2048w" sizes="(max-width: 1280px) 100vw, 1280px"/></figure ><figure class="tmblr-full" data-orig-height="1080" data-orig-width="1920"><img src="https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s640x960/f0dfd4aa4229944da9b0e525e6c9a0ec89143373.png" data-orig-height="1080" data-orig-width="1920" srcset="https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s75x75_c1/0268965777520275909841726829a2571b4dd4b1.png 75w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s100x200/e3b79f463b947c8857dddceda099e4cfe6e53e9c.png 100w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s250x400/4eeb69a20d2872719cf1e2e28b54367d28a33114.png 250w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s400x600/445c4b2e690ae3075b362a105af9b4aa45adee88.png 400w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s500x750/30a34daebebee02fc501e9ca7361d6f3221ed856.png 500w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s540x810/29f17a07b007f7476d4a321a56a29f333c32b93e.png 540w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s640x960/f0dfd4aa4229944da9b0e525e6c9a0ec89143373.png 640w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s1280x1920/6b40fc10a77481213526f9f4412aca16bc0ce173.png 1280w, https://64.media.tumblr.com/4175b0d1d5a1b0fd5c0fe1bfe7b50c03/217c86a94d5179ce-c1/s2048x3072/286ccace479893c0e6a7803080132f3b7869b634.png 1920w" sizes="(max-width: 1280px) 100vw, 1280px"/></figure ></div ><p ><b >Source: </b ></p ><p >The Game Awards 2023. December 7, 2023. </p ></blockquote >',
|
||||
'publishedAt': 1702220536,
|
||||
}, {
|
||||
'id':
|
||||
'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c-eb92be444fd1f7418a53a146c4b6366c',
|
||||
'userId': 'myuser',
|
||||
'columnId': 'mycolumn',
|
||||
'sourceId': 'tumblr-myuser-mycolumn-8ce77e730a91955cd991349d0d6bfb6c',
|
||||
'title': 'Hardest image in gaming culture',
|
||||
'link': 'https://www.tumblr.com/todayontumblr/736327882138386432',
|
||||
'media':
|
||||
'https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s640x960/8ce0d3c4fa8efea0a196cb1921aa64cc43950f8d.png',
|
||||
'description':
|
||||
'<p ><a class="tumblr_blog" href="https://www.tumblr.com/temperedknight/736102767863742464/hardest-image-in-gaming-culture">temperedknight </a >:</p ><blockquote ><div class="npf_row"><figure class="tmblr-full" data-orig-height="521" data-orig-width="1063"><img src="https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s640x960/8ce0d3c4fa8efea0a196cb1921aa64cc43950f8d.png" data-orig-height="521" data-orig-width="1063" srcset="https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s75x75_c1/c86df7bd8018d29a3848bf9ebf68c645f3eb43ff.png 75w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s100x200/4d9c7c121ef92a8ba19983b2a5a56e5406a03e0c.png 100w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s250x400/2cf0925fa1dfd3fae5a991e88453f024be044f70.png 250w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s400x600/88f6685b3a8f0bafeb83ccb875d82d29269b268d.png 400w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s500x750/e084806a49fb3808cd10d8916bcaec53493fc181.png 500w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s540x810/4630feda50c2ec3c41a7d7413cae5f853cdb5034.png 540w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s640x960/8ce0d3c4fa8efea0a196cb1921aa64cc43950f8d.png 640w, https://64.media.tumblr.com/363392fc016d8c904fa3b86050b366a2/08a786e18a31fca4-84/s1280x1920/c48e8fb2dad3ff167d0a318025c6edfff19a49fc.png 1063w" sizes="(max-width: 1063px) 100vw, 1063px"/></figure ></div ><p >Hardest image in gaming culture </p ></blockquote >',
|
||||
'publishedAt': 1702216989,
|
||||
}]);
|
||||
} finally {
|
||||
fetchWithTimeoutSpy.restore();
|
||||
}
|
||||
|
||||
assertSpyCall(fetchWithTimeoutSpy, 0, {
|
||||
args: [
|
||||
'https://todayontumblr.tumblr.com/rss',
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
],
|
||||
returned: new Promise((resolve) => {
|
||||
resolve(new Response(response, { status: 200 }));
|
||||
}),
|
||||
});
|
||||
assertSpyCalls(fetchWithTimeoutSpy, 1);
|
||||
});
|
||||
12
supabase/functions/_shared/feed/utils/index.ts
Normal file
12
supabase/functions/_shared/feed/utils/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getFavicon } from './getFavicon.ts';
|
||||
import { uploadSourceIcon } from './uploadFile.ts';
|
||||
import { assertEqualsItems, assertEqualsSource } from './test.ts';
|
||||
|
||||
export type { Favicon } from './getFavicon.ts';
|
||||
|
||||
export const feedutils = {
|
||||
getFavicon,
|
||||
uploadSourceIcon,
|
||||
assertEqualsItems,
|
||||
assertEqualsSource,
|
||||
};
|
||||
32
supabase/functions/_shared/feed/utils/test.ts
Normal file
32
supabase/functions/_shared/feed/utils/test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { assertEquals } from 'std/assert';
|
||||
|
||||
import { ISource } from '../../models/source.ts';
|
||||
import { IItem } from '../../models/item.ts';
|
||||
|
||||
export const assertEqualsSource = (actual: ISource, expected: ISource) => {
|
||||
assertEquals(actual.id, expected.id);
|
||||
assertEquals(actual.columnId, expected.columnId);
|
||||
assertEquals(actual.userId, expected.userId);
|
||||
assertEquals(actual.type, expected.type);
|
||||
assertEquals(actual.title, expected.title);
|
||||
assertEquals(actual.options, expected.options);
|
||||
assertEquals(actual.link, expected.link);
|
||||
assertEquals(actual.icon, expected.icon);
|
||||
};
|
||||
|
||||
export const assertEqualsItems = (actual: IItem[], expected: IItem[]) => {
|
||||
assertEquals(actual.length, expected.length);
|
||||
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
assertEquals(actual[i].id, expected[i].id);
|
||||
assertEquals(actual[i].columnId, expected[i].columnId);
|
||||
assertEquals(actual[i].userId, expected[i].userId);
|
||||
assertEquals(actual[i].sourceId, expected[i].sourceId);
|
||||
assertEquals(actual[i].title, expected[i].title);
|
||||
assertEquals(actual[i].link, expected[i].link);
|
||||
assertEquals(actual[i].media, expected[i].media);
|
||||
assertEquals(actual[i].description, expected[i].description);
|
||||
assertEquals(actual[i].author, expected[i].author);
|
||||
assertEquals(actual[i].options, expected[i].options);
|
||||
}
|
||||
};
|
||||
@@ -7,11 +7,10 @@ import { unescape } from 'lodash';
|
||||
|
||||
import { IItem } from '../models/item.ts';
|
||||
import { ISource } from '../models/source.ts';
|
||||
import { uploadSourceIcon } from './utils/uploadFile.ts';
|
||||
import { feedutils } from './utils/index.ts';
|
||||
import { IProfile } from '../models/profile.ts';
|
||||
import { fetchWithTimeout } from '../utils/fetchWithTimeout.ts';
|
||||
import { FEEDDECK_SOURCE_YOUTUBE_API_KEY } from '../utils/constants.ts';
|
||||
import { log } from '../utils/log.ts';
|
||||
import { utils } from '../utils/index.ts';
|
||||
|
||||
/**
|
||||
* `isYoutubeUrl` checks if the provided `url` is a valid Youtube url. A url is
|
||||
@@ -76,11 +75,11 @@ export const getYoutubeFeed = async (
|
||||
* Get the RSS for the provided `youtube` url and parse it. If a feed doesn't
|
||||
* contains an item we return an error.
|
||||
*/
|
||||
const response = await fetchWithTimeout(source.options.youtube, {
|
||||
const response = await utils.fetchWithTimeout(source.options.youtube, {
|
||||
method: 'get',
|
||||
}, 5000);
|
||||
const xml = await response.text();
|
||||
log('debug', 'Add source', {
|
||||
utils.log('debug', 'Add source', {
|
||||
sourceType: 'youtube',
|
||||
requestUrl: source.options.youtube,
|
||||
responseStatus: response.status,
|
||||
@@ -103,7 +102,7 @@ export const getYoutubeFeed = async (
|
||||
'',
|
||||
),
|
||||
);
|
||||
source.icon = await uploadSourceIcon(supabaseClient, source);
|
||||
source.icon = await feedutils.uploadSourceIcon(supabaseClient, source);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,7 +262,7 @@ const getMedia = (entry: FeedEntry): string | undefined => {
|
||||
*/
|
||||
const getChannelId = async (url: string): Promise<string | undefined> => {
|
||||
try {
|
||||
const response = await fetchWithTimeout(url, { method: 'get' }, 5000);
|
||||
const response = await utils.fetchWithTimeout(url, { method: 'get' }, 5000);
|
||||
const html = await response.text();
|
||||
const match = html.match(
|
||||
/"https:\/\/www.youtube.com\/feeds\/videos.xml\?channel_id\=(.*?)"/,
|
||||
@@ -288,14 +287,9 @@ const getChannelId = async (url: string): Promise<string | undefined> => {
|
||||
const getChannelIcon = async (
|
||||
channelId: string,
|
||||
): Promise<string | undefined> => {
|
||||
const youtubeApiKey = FEEDDECK_SOURCE_YOUTUBE_API_KEY;
|
||||
if (!youtubeApiKey) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetchWithTimeout(
|
||||
`https://www.googleapis.com/youtube/v3/channels?id=${channelId}&part=id%2Csnippet&maxResults=1&key=${youtubeApiKey}`,
|
||||
const response = await utils.fetchWithTimeout(
|
||||
`https://www.googleapis.com/youtube/v3/channels?id=${channelId}&part=id%2Csnippet&maxResults=1&key=${FEEDDECK_SOURCE_YOUTUBE_API_KEY}`,
|
||||
{ method: 'get' },
|
||||
5000,
|
||||
);
|
||||
|
||||
364
supabase/functions/_shared/feed/youtube_test.ts
Normal file
364
supabase/functions/_shared/feed/youtube_test.ts
Normal file
File diff suppressed because one or more lines are too long
7
supabase/functions/_shared/utils/index.ts
Normal file
7
supabase/functions/_shared/utils/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { fetchWithTimeout } from './fetchWithTimeout.ts';
|
||||
import { log } from './log.ts';
|
||||
|
||||
export const utils = {
|
||||
fetchWithTimeout,
|
||||
log,
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"imports": {
|
||||
"std/assert": "https://deno.land/std@0.208.0/assert/mod.ts",
|
||||
"std/testing/mock": "https://deno.land/std@0.208.0/testing/mock.ts",
|
||||
"std/server": "https://deno.land/std@0.177.0/http/server.ts",
|
||||
"std/md5": "https://deno.land/std@0.119.0/hash/md5.ts",
|
||||
"std/hex": "https://deno.land/std@0.177.0/encoding/hex.ts",
|
||||
|
||||
Reference in New Issue
Block a user