<script lang="ts">
import type { Forum } from '$lib/data/types';
- import { topicsForForum } from '$lib/stores/topics';
export let forum: Forum;
<h1 class="py-4 font-bold text-3xl">{forum.glyph} {$_(forum.label)}</h1>
<ul class="list-disc list-inside pl-2">
- {#if forum.topics}
- {#each forum.topics as topic}
- <TopicSummary {topic} />
- {/each}
- {/if}
+ {#if forum.topics}
+ {#each forum.topics as topic}
+ <TopicSummary {topic} />
+ {/each}
+ {/if}
</ul>
export let forums: Forum[];
import { _ } from 'svelte-i18n';
- $: sortedForums = forums
- .slice()
- .sort((a, b) => a.position - b.position);
+ $: sortedForums = forums.slice().sort((a, b) => a.position - b.position);
</script>
<ul class="p-0 pl-2">
export let uuid: string;
</script>
-<div class="inline-block w-12 h-12 mt-1 bg-white border border-black border-solid p-0.5 box-content glyphicon" role="img" aria-label={$_('glyph.title')} title={$_('glyph.title')}>
+<div
+ class="inline-block w-12 h-12 mt-1 bg-white border border-black border-solid p-0.5 box-content glyphicon"
+ role="img"
+ aria-label={$_('glyph.title')}
+ title={$_('glyph.title')}
+>
{#each getGlyphHash(uuid) as fragment}
- <span class="block text-2xl float-left w-6 h-6 text-center leading-6 {fragment.glyph}" style="color: {fragment.color} ">
+ <span
+ class="block text-2xl float-left w-6 h-6 text-center leading-6 {fragment.glyph}"
+ style="color: {fragment.color} "
+ >
{fragment.glyph}
</span>
{/each}
title={$_('post.metadata_title', { values: { count: index + 1, total: count } })}
>
<Glyph uuid={post.author_id} />
- {#if post.author}
- <span class="h-card">
- {$_('post.author_credit')}
- <a href="/a/{post.author.handle}" class="p-nickname u-url underline text-blue-600 visited:text-purple-500">{post.author.handle}</a>.
- </span>
- {:else}
- <span>????</span>
- {/if}
+ {#if post.author}
+ <span class="h-card">
+ {$_('post.author_credit')}
+ <a
+ href="/a/{post.author.handle}"
+ class="p-nickname u-url underline text-blue-600 visited:text-purple-500"
+ >{post.author.handle}</a
+ >.
+ </span>
+ {:else}
+ <span>????</span>
+ {/if}
<time role="presentation" class="dt-published" datetime={timestampToISO(post.created_at)}>
- <a title={$_('post.permalink_title')} class="underline text-blue-600 visited:text-purple-500" href="/p/{post.id}">
+ <a
+ title={$_('post.permalink_title')}
+ class="underline text-blue-600 visited:text-purple-500"
+ href="/p/{post.id}"
+ >
{timestampToISO(post.created_at)}
</a>
</time>
{#if post.topic}
<span>
- ({$_('post.topic_location')} <a class="text-blue-600 underline visited:text-purple-500" href="/t/{post.topic.id}">{post.topic.title}</a>.)
+ ({$_('post.topic_location')}
+ <a class="text-blue-600 underline visited:text-purple-500" href="/t/{post.topic.id}"
+ >{post.topic.title}</a
+ >.)
</span>
{/if}
</aside>
<article
class="e-content whitespace-pre"
title={$_('post.title', {
- values: { count: index + 1, total: count, author: post.author?.handle || '????' }
+ values: { count: index + 1, total: count, author: post.author?.handle || '????' }
})}
>
{post.text}
{#if topic.forum}
<span class="topic-location">
{$_('topic.category_location')}
- <a href="/f/{topic.forum.id}" class="p-category underline text-blue-600 visited:text-purple-500">
+ <a
+ href="/f/{topic.forum.id}"
+ class="p-category underline text-blue-600 visited:text-purple-500"
+ >
{topic.forum.glyph}
{$_(topic.forum.label)}
</a>.
</span>
{/if}
<span class="topic-ttl">
- <a class="u-url u-uid underline text-blue-600 visited:text-purple-500" title={$_('topic.permalink_title')} href="/t/{topic.id}">
+ <a
+ class="u-url u-uid underline text-blue-600 visited:text-purple-500"
+ title={$_('topic.permalink_title')}
+ href="/t/{topic.id}"
+ >
({$_('topic.remaining_time', {
values: { remaining: $_(remaining.label, { values: { count: remaining.count } }) }
})})
{#each topic.tags as tag}
<a href="/g/{tag.tag}" class="p-category underline text-blue-600 visited:text-purple-500">
{tag.tag}<span class="tag-weight">({tag.count})</span></a
- >{' '}
+ >{' '}
{/each}
</aside>
{/if}
{#if topic.posts}
- {#each topic.posts as post, index}
- <Post {post} {index} count={topic.posts.length} />
- {/each}
+ {#each topic.posts as post, index}
+ <Post {post} {index} count={topic.posts.length} />
+ {/each}
{/if}
</div>
<li class="h-entry" title={$_('topic.title')}>
<span class="p-name">
- <a class="u-url u-uid underline text-blue-600 visited:text-purple-500" title={$_('topic.permalink_title')} href="/t/{topic.id}">
+ <a
+ class="u-url u-uid underline text-blue-600 visited:text-purple-500"
+ title={$_('topic.permalink_title')}
+ href="/t/{topic.id}"
+ >
{topic.title}
</a></span
>
title: string;
ttl: number;
updated_at: number;
- forum_id: string;
- forum?: Forum;
- posts?: Post[];
- tags?: Tag[];
+ forum_id: string;
+ forum?: Forum;
+ posts?: Post[];
+ tags?: Tag[];
};
export type Post = {
text: string;
topic_id: string;
topic?: Topic;
- author_id: string;
- author?: User;
+ author_id: string;
+ author?: User;
};
export type Tag = {
-import { createClient } from '@supabase/supabase-js'
+import { createClient } from '@supabase/supabase-js';
import { single, collection } from './supabase';
import { supabase } from '$lib/config/config';
const client = createClient(supabase.url, supabase.key);
-export const forum = (id: string, withTopics = false) => single<Forum>(client
- .from('forums')
- .select(withTopics ? `*,
+export const forum = (id: string, withTopics = false) =>
+ single<Forum>(
+ client
+ .from('forums')
+ .select(
+ withTopics
+ ? `*,
topics (
*
)
- `: '*' )
- .eq('id', id),
- null);
+ `
+ : '*'
+ )
+ .eq('id', id),
+ null
+ );
export const forums = collection<Forum>(client.from('forums').select('*'), []);
-import { createClient } from '@supabase/supabase-js'
-import { single, collection } from './supabase';
+import { createClient } from '@supabase/supabase-js';
+import { single } from './supabase';
import { supabase } from '$lib/config/config';
import type { Post } from '$lib/data/types';
const client = createClient(supabase.url, supabase.key);
-export const post = (id: string, withTopic = false) => single<Post>(client
- .from('posts')
- .select(withTopic ? `*,
+export const post = (id: string, withTopic = false) =>
+ single<Post>(
+ client
+ .from('posts')
+ .select(
+ withTopic
+ ? `*,
topic:topic_id (
*
)
- `: '*' )
- .eq('id', id),
- null);
-export const postsForTopic = (id: string) => collection<Post>(client
- .from('posts')
- .select('*')
- .eq('topic_id', id)
- .order('created_at', { ascending: true }),
- []);
+ `
+ : '*'
+ )
+ .eq('id', id),
+ null
+ );
*/
interface AnyError {
- message: string
-};
+ message: string;
+}
export type StoreState<Type> = {
loading: boolean;
error: AnyError | void;
};
-export function initialResponse<T> (initialState: T): StoreState<T> {
- return {
- loading: true,
- data: initialState,
- error: undefined
- };
+export function initialResponse<T>(initialState: T): StoreState<T> {
+ return {
+ loading: true,
+ data: initialState,
+ error: undefined
+ };
}
-export function errorResponse<T> (error: AnyError): StoreState<T> {
- return {
- loading: false,
- data: undefined,
- error
- };
+export function errorResponse<T>(error: AnyError): StoreState<T> {
+ return {
+ loading: false,
+ data: undefined,
+ error
+ };
}
-export function response<T> (data: T): StoreState<T> {
- return {
- loading: false,
- data,
- error: undefined
- };
+export function response<T>(data: T): StoreState<T> {
+ return {
+ loading: false,
+ data,
+ error: undefined
+ };
}
import type { PostgrestFilterBuilder } from '@supabase/postgrest-js';
import type { StoreState } from './response_builder';
-export function collection<T> (query: PostgrestFilterBuilder<T>, initialValue: T[]): Readable<StoreState<T[]>> {
-
- return readable(initialResponse<T[]>(initialValue), (set) => {
-
- (async function() {
- const { data, error } = await query;
-
- if (error) {
- return set(errorResponse<T[]>(error));
- }
-
- set(response<T[]>(data));
- })()
- });
+export function collection<T>(
+ query: PostgrestFilterBuilder<T>,
+ initialValue: T[]
+): Readable<StoreState<T[]>> {
+ return readable(initialResponse<T[]>(initialValue), (set) => {
+ (async function () {
+ const { data, error } = await query;
+
+ if (error) {
+ return set(errorResponse<T[]>(error));
+ }
+
+ set(response<T[]>(data));
+ })();
+ });
}
-export function single<T> (query: PostgrestFilterBuilder<T>, initialValue: T): Readable<StoreState<T>> {
-
- return readable(initialResponse<T>(initialValue), (set) => {
-
- (async function() {
- const { data, error } = await query.single();
-
- if (error) {
- return set(errorResponse<T>(error));
- }
-
- set(response<T>(data));
- })()
- });
+export function single<T>(
+ query: PostgrestFilterBuilder<T>,
+ initialValue: T
+): Readable<StoreState<T>> {
+ return readable(initialResponse<T>(initialValue), (set) => {
+ (async function () {
+ const { data, error } = await query.single();
+
+ if (error) {
+ return set(errorResponse<T>(error));
+ }
+
+ set(response<T>(data));
+ })();
+ });
}
+++ /dev/null
-import { GraphQLInteraction, Pact, Matchers } from '@pact-foundation/pact';
-import { resolve } from 'path';
-
-import { resolveAfter } from '$lib/utils/resolve_after';
-
-const { eachLike, like } = Matchers;
-
-jest.mock('$lib/config/config.ts');
-
-import { getTag } from './tags';
-
-const internals = {
- provider: null
-};
-
-describe('Tags store pact', () => {
- beforeAll(async () => {
- internals.provider = new Pact({
- port: 1234,
- dir: resolve(process.cwd(), 'pacts'),
- consumer: 'ForumClient',
- provider: 'ForumServer',
- pactfileWriteMode: 'update'
- });
-
- await internals.provider.setup();
- });
-
- afterEach(() => internals.provider.verify());
- afterAll(() => internals.provider.finalize());
-
- describe("When there's data", () => {
- describe('GetTag', () => {
- beforeAll(async () => {
- const tagQuery = new GraphQLInteraction()
- .given("there's data")
- .uponReceiving('a request to get a single tag')
- .withRequest({
- path: '/graphql',
- method: 'POST'
- })
- .withOperation('GetTag')
- .withQuery(
- `query GetTag($id: ID!) {
- tag(id: $id) {
- id
- topics {
- id
- title
- updated_at
- ttl
- __typename
- }
- __typename
- }
- }`
- )
- .withVariables({
- id: 'pineapple'
- })
- .willRespondWith({
- status: 200,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8'
- },
- body: {
- data: {
- tag: {
- id: like('pineapple'),
- topics: eachLike({
- id: like('cd038ae7-e8b4-4e38-9543-3d697e69ac34'),
- title: like('This topic is about pineapples'),
- updated_at: like(1619978944077),
- ttl: like(3555)
- })
- }
- }
- }
- });
- return await internals.provider.addInteraction(tagQuery);
- });
-
- test('it returns the tag', async () => {
- const tag = getTag('pineapple');
- const { counter, promise: resolveAfterTwo } = resolveAfter(2);
- let response = null;
- tag.subscribe((tagValue) => {
- response = tagValue;
- counter();
- });
- expect(response.data).toBe(null);
- expect(response.loading).toBe(true);
- expect(response.error).toBe(undefined);
- await resolveAfterTwo;
- expect(response.data).toEqual({
- id: 'pineapple',
- topics: [
- {
- id: 'cd038ae7-e8b4-4e38-9543-3d697e69ac34',
- title: 'This topic is about pineapples',
- updated_at: 1619978944077,
- ttl: 3555
- }
- ]
- });
- expect(response.loading).toBe(false);
- expect(response.error).toBe(undefined);
- });
- });
- });
-
- describe("When there's no data", () => {
- describe('GetTag', () => {
- beforeAll(async () => {
- const tagQuery = new GraphQLInteraction()
- .given("there's no data")
- .uponReceiving('a request to get a single tag')
- .withRequest({
- path: '/graphql',
- method: 'POST'
- })
- .withOperation('GetTag')
- .withQuery(
- `query GetTag($id: ID!) {
- tag(id: $id) {
- id
- topics {
- id
- title
- updated_at
- ttl
- __typename
- }
- __typename
- }
- }`
- )
- .withVariables({
- id: 'pineapple'
- })
- .willRespondWith({
- status: 200,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8'
- },
- body: {
- data: {
- tag: null
- }
- }
- });
- return await internals.provider.addInteraction(tagQuery);
- });
-
- test('it returns the tag', async () => {
- const tag = getTag('pineapple');
- const { counter, promise: resolveAfterTwo } = resolveAfter(2);
- let response = null;
- tag.subscribe((tagValue) => {
- response = tagValue;
- counter();
- });
- expect(response.data).toBe(null);
- expect(response.loading).toBe(true);
- expect(response.error).toBe(undefined);
- await resolveAfterTwo;
- expect(response.data).toBe(null);
- expect(response.loading).toBe(false);
- expect(response.error).toBe(undefined);
- });
- });
- });
-
- describe("When there's a server error", () => {
- describe('GetTag', () => {
- beforeAll(async () => {
- const tagQuery = new GraphQLInteraction()
- .given("there's a server error")
- .uponReceiving('a request to get a single tag')
- .withRequest({
- path: '/graphql',
- method: 'POST'
- })
- .withOperation('GetTag')
- .withQuery(
- `query GetTag($id: ID!) {
- tag(id: $id) {
- id
- topics {
- id
- title
- updated_at
- ttl
- __typename
- }
- __typename
- }
- }`
- )
- .withVariables({
- id: 'pineapple'
- })
- .willRespondWith({
- status: 500
- });
- return await internals.provider.addInteraction(tagQuery);
- });
-
- test('it returns the error', async () => {
- const tag = getTag('pineapple');
- const { counter, promise: resolveAfterTwo } = resolveAfter(2);
- let response = null;
- tag.subscribe((tagValue) => {
- response = tagValue;
- counter();
- });
- expect(response.data).toBe(null);
- expect(response.loading).toBe(true);
- expect(response.error).toBe(undefined);
- await resolveAfterTwo;
- expect(response.data).toBe(null);
- expect(response.loading).toBe(false);
- expect(response.error).toBeInstanceOf(Error);
- });
- });
- });
-
- describe("When there's an error in the response", () => {
- describe('GetTag', () => {
- beforeAll(async () => {
- const tagQuery = new GraphQLInteraction()
- .given("there's an error in the response")
- .uponReceiving('a request to get a single tag')
- .withRequest({
- path: '/graphql',
- method: 'POST'
- })
- .withOperation('GetTag')
- .withQuery(
- `query GetTag($id: ID!) {
- tag(id: $id) {
- id
- topics {
- id
- title
- updated_at
- ttl
- __typename
- }
- __typename
- }
- }`
- )
- .withVariables({
- id: 'pineapple'
- })
- .willRespondWith({
- status: 200,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8'
- },
- body: {
- errors: eachLike({
- message: like('An error occurred when fetching the tag')
- })
- }
- });
- return await internals.provider.addInteraction(tagQuery);
- });
-
- test('it returns the error', async () => {
- const tag = getTag('pineapple');
- const { counter, promise: resolveAfterTwo } = resolveAfter(2);
- let response = null;
- tag.subscribe((tagValue) => {
- response = tagValue;
- counter();
- });
- expect(response.data).toBe(null);
- expect(response.loading).toBe(true);
- expect(response.error).toBe(undefined);
- await resolveAfterTwo;
- expect(response.data).toBe(null);
- expect(response.loading).toBe(false);
- expect(response.error.graphQLErrors).toEqual(
- expect.arrayContaining([
- {
- message: 'An error occurred when fetching the tag'
- }
- ])
- );
- });
- });
- });
-});
+++ /dev/null
-import { createClient } from '@supabase/supabase-js'
-import { collection } from './supabase';
-import { supabase } from '$lib/config/config';
-
-import type { Tag } from '$lib/data/types';
-
-const client = createClient(supabase.url, supabase.key);
-
-export const tagsForTopic = (id: string) => collection<Tag>(client
- .from('topic_tags')
- .select('*')
- .eq('topic_id', id),
- []);
-import { createClient } from '@supabase/supabase-js'
+import { createClient } from '@supabase/supabase-js';
import { single, collection } from './supabase';
import { supabase } from '$lib/config/config';
const client = createClient(supabase.url, supabase.key);
-export const topic = (id: string, withPosts = false) => single<Topic>(client
- .from('topics')
- .select(withPosts ? `*,
+export const topic = (id: string, withPosts = false) =>
+ single<Topic>(
+ client
+ .from('topics')
+ .select(
+ withPosts
+ ? `*,
forum: forums (*),
tags: topic_tags (*),
posts (
*,
author:author_id (*)
)
- `: '*' )
- .eq('id', id),
- null);
-export const topicsForForum = (id: string) => collection<Topic>(client
- .from('topics')
- .select('*')
- .eq('forum_id', id),
- []);
-export const topicsForTag = (id: string) => collection<Topic>(client
- .from('topics')
- .select(`
+ `
+ : '*'
+ )
+ .eq('id', id),
+ null
+ );
+export const topicsForTag = (id: string) =>
+ collection<Topic>(
+ client
+ .from('topics')
+ .select(
+ `
*,tags!inner(*)
- `)
- .eq('tags.tag', id),
- []);
+ `
+ )
+ .eq('tags.tag', id),
+ []
+ );
+++ /dev/null
-import { createClient } from '@supabase/supabase-js'
-import { single } from './supabase';
-import { supabase } from '$lib/config/config';
-
-import type { User } from '$lib/data/types';
-
-const client = createClient(supabase.url, supabase.key);
-
-export const user = (id: string) => single<User>(client
- .from('users')
- .select('*')
- .eq('id', id),
- null);
<script lang="ts" context="module">
- export const load = ({
- params: { id }
- }) => ({ props: { id } });
+ export const load = ({ params: { id } }) => ({ props: { id } });
</script>
<script lang="ts">
<script lang="ts" context="module">
- export const load = ({
- params: { id }
- }) => ({ props: { id } });
+ export const load = ({ params: { id } }) => ({ props: { id } });
</script>
<script lang="ts">
<script lang="ts" context="module">
- export const load = ({
- params: { id }
- }) => ({ props: { id } });
+ export const load = ({ params: { id } }) => ({ props: { id } });
</script>
<script lang="ts">
<script lang="ts" context="module">
- export const load = ({
- params: { id }
- }) => ({ props: { id } });
+ export const load = ({ params: { id } }) => ({ props: { id } });
</script>
<script lang="ts">
<script lang="ts" context="module">
- export const load = ({
- params: { id }
- }) => ({ props: { id } });
+ export const load = ({ params: { id } }) => ({ props: { id } });
</script>
<script lang="ts">