Skip to content

Commit

Permalink
Api additions (#375)
Browse files Browse the repository at this point in the history
* add sections select to page api, add search endpoint

* couple search fixes

* let's try query parameter instead

* multiple keywords search fix

* move '/websearch' to '?search', fix section query from root page

---------

Co-authored-by: Mateo Morris <[email protected]>
  • Loading branch information
rallisf1 and mateomorris authored Feb 9, 2024
1 parent cd9ae80 commit 041c77b
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 78 deletions.
38 changes: 37 additions & 1 deletion primo_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -448,4 +448,40 @@ CREATE POLICY "Give Authenticated users access to delete images" ON storage.obje
(bucket_id = 'images' :: text)
AND (auth.role() = 'authenticated' :: text)
)
);
);

-- helper functions
CREATE OR REPLACE FUNCTION page_search(search_terms text, site_url text)
RETURNS TABLE(id uuid, name text, url text, created_at timestamp with time zone) AS $$
BEGIN
RETURN QUERY
WITH RECURSIVE parent_urls AS (
SELECT
p.id,
ARRAY[p.url] AS urls
FROM pages p
INNER JOIN sites s ON p.site = s.id
WHERE p.parent IS NULL AND s.url = site_url

UNION ALL

SELECT
p.id,
pu.urls || p.url
FROM pages p
INNER JOIN sites s ON p.site = s.id
INNER JOIN parent_urls pu ON p.parent = pu.id
WHERE s.url = site_url
)
SELECT DISTINCT
p.id,
p.name,
ARRAY_TO_STRING(parent_urls.urls, '/', '/') AS url,
p.created_at
FROM pages p
INNER JOIN sites s ON p.site = s.id
INNER JOIN sections se ON p.id = se.page
INNER JOIN parent_urls ON p.id = parent_urls.id
WHERE s.url = site_url AND to_tsvector(se.content) @@ to_tsquery(search_terms);
END;
$$ LANGUAGE plpgsql;
73 changes: 42 additions & 31 deletions src/routes/api/[site]/+server.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import { json } from '@sveltejs/kit';
import { json } from '@sveltejs/kit'
import supabase_admin from '$lib/supabase/admin'
import page_search from './page-search'

export async function GET({ params }) {
export async function GET({ url, params }) {
if (url.searchParams.has('search')) {
return await page_search(url, params.site)
}

const [{data:site_res},{data:page_res},{data:subpages_data, error:subpages_error},{data:sections_res}] = await Promise.all([
supabase_admin.from('sites').select().filter('url', 'eq', params.site).single(),
supabase_admin.from('pages').select('*, site!inner(url)').match({ url: 'index', 'site.url': params.site }).single(),
supabase_admin.from('pages').select('*, site!inner(url)').match({ 'site.url': params.site }),
supabase_admin.from('sections').select('*, page!inner( site!inner(url) )').match({
'page.site.url': params.site,
'page.url': 'index'
})
])
const [{ data: site_res }, { data: page_res }, { data: subpages_data }, { data: sections_res }] =
await Promise.all([
supabase_admin.from('sites').select().filter('url', 'eq', params.site).single(),
supabase_admin
.from('pages')
.select('*, site!inner(url)')
.match({ url: 'index', 'site.url': params.site })
.single(),
supabase_admin.from('pages').select('*, site!inner(url)').match({ 'site.url': params.site }),
supabase_admin.from('sections').select('*, page!inner( site!inner(url) )').match({
'page.site.url': params.site,
'page.url': 'index',
}),
])

const site = {
...site_res['content']['en'],
_meta: {
id: site_res.id,
name: site_res.name,
url: site_res.url,
created_at: site_res.created_at
}
created_at: site_res.created_at,
},
}

const page = {
Expand All @@ -31,29 +40,31 @@ export async function GET({ params }) {
url: page_res.url,
created_at: page_res.created_at,
// filtering here because the query above is not filtering properly (maybe a Supabase bug)
subpages: subpages_data?.filter(subpage => subpage.parent === null && subpage.url !== 'index').map(subpage => ({
id: subpage.id,
name: subpage.name,
url: subpage.url,
created_at: subpage.created_at
}))
subpages: subpages_data
?.filter((subpage) => subpage.parent === null && subpage.url !== 'index')
.map((subpage) => ({
id: subpage.id,
name: subpage.name,
url: subpage.url,
created_at: subpage.created_at,
})),
},
}


const sections = sections_res?.sort((a,b) => a.index - b.index).map(section => ({
...section.content.en,
_meta: {
id: section.id,
symbol: section.symbol,
created_at: section.created_at
}
}))
const sections = sections_res
?.sort((a, b) => a.index - b.index)
.map((section) => ({
...section.content.en,
_meta: {
id: section.id,
symbol: section.symbol,
created_at: section.created_at,
},
}))

return json({
site,
page,
sections
sections,
})

}
}
151 changes: 105 additions & 46 deletions src/routes/api/[site]/[...page]/+server.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { json } from '@sveltejs/kit';
import { json } from '@sveltejs/kit'
import supabase_admin from '$lib/supabase/admin'
import { languages } from '@primocms/builder'

export async function GET({ url, params }) {

const pages = params.page?.split('/') || []
const lang = languages.some(lang => lang.key === pages[0]) ? pages.pop() : 'en'
const lang = languages.some((lang) => lang.key === pages[0]) ? pages.pop() : 'en'
const page_url = pages.pop() || 'index'
const parent_url = pages.pop() || null

let options = {
const options = {
format: 'html', // html | markdown
range: '0,9', // from,to # https://supabase.com/docs/reference/javascript/range
sort: 'created_at,desc' // created_at | name, asc | desc # default returns latest
sort: 'created_at,desc', // created_at | name, asc | desc # default returns latest
sections: '*', // limit the results to specific comma separated section ids
}

for (const p of url.searchParams) {
Expand All @@ -21,18 +21,62 @@ export async function GET({ url, params }) {
}
}

const [{ data: site_data }, { data: page_data }, { data: subpages_data, error: subpages_error }, { count: subpages_total }, { data: sections_data }] = await Promise.all([
const [
{ data: site_data },
{ data: page_data },
{ data: subpages_data },
{ count: subpages_total },
{ data: sections_data },
] = await Promise.all([
supabase_admin.from('sites').select().filter('url', 'eq', params.site).single(),
supabase_admin.from('pages').select('*, site!inner(url)').match({ url: page_url, 'site.url': params.site }).single(),
supabase_admin.from('pages').select('*, parent!inner(*), site!inner(url)').match({ 'site.url': params.site, 'parent.url': page_url })
.order(options.sort.split(',')[0], { ascending: options.sort.split(',')[1] === 'asc' })
supabase_admin
.from('pages')
.select('*, site!inner(url)')
.match({ url: page_url, 'site.url': params.site })
.single(),
supabase_admin
.from('pages')
.select('*, parent!inner(*), site!inner(url)')
.match({ 'site.url': params.site, 'parent.url': page_url })
.order(options.sort.split(',')[0], {
ascending: options.sort.split(',')[1] === 'asc',
})
.range(parseInt(options.range.split(',')[0]), parseInt(options.range.split(',')[1])),
supabase_admin.from('pages').select('*, parent!inner(url), site!inner(url)', { count: 'exact', head: true }).match({ 'site.url': params.site, 'parent.url': page_url }),
supabase_admin.from('sections').select('*, symbol!inner(name, content), page!inner( site!inner(url), parent!inner(url) )').match({
'page.site.url': params.site,
'page.parent.url': parent_url,
'page.url': page_url
}).order('index')
supabase_admin
.from('pages')
.select('*, parent!inner(url), site!inner(url)', {
count: 'exact',
head: true,
})
.match({ 'site.url': params.site, 'parent.url': page_url }),
options.sections === '*'
? supabase_admin
.from('sections')
.select(
parent_url
? `*, symbol(name, content), page!inner( url, site!inner(url), parent!inner(url) )`
: `*, symbol(name, content), page!inner( url, site!inner(url) )`
)
.match({
'page.url': page_url,
'page.site.url': params.site,
...(parent_url ? { 'page.parent.url': parent_url } : {}),
})
.order('index')
: supabase_admin
.from('sections')
.select(
parent_url
? `*, symbol(name, content), page!inner( url, site!inner(url), parent!inner(url) )`
: `*, poo:content->en, symbol(name, content), page!inner( url, site!inner(url) )`
)
.match({
'page.url': page_url,
'page.site.url': params.site,
...(parent_url ? { 'page.parent.url': parent_url } : {}),
})
.in('id', options.sections.split(','))
.order('index'),
])

const site = {
Expand All @@ -42,10 +86,12 @@ export async function GET({ url, params }) {
id: site_data.id,
name: site_data.name,
url: site_data.url,
created_at: site_data.created_at
}
created_at: site_data.created_at,
},
}

console.log({ sections_data })

const page = {
// @ts-ignore
...page_data['content'][lang],
Expand All @@ -55,38 +101,18 @@ export async function GET({ url, params }) {
url: page_data.url,
created_at: page_data.created_at,
subpages_total,
subpages: subpages_data?.map(subpage => ({
subpages: subpages_data?.map((subpage) => ({
id: subpage.id,
name: subpage.name,
url: subpage.url,
created_at: subpage.created_at
}))
created_at: subpage.created_at,
})),
},
}

const formatContent = (sections) => {
if (Array.isArray(sections)) {
sections.forEach(item => formatContent(item))
} else if (typeof sections === 'object' && sections !== null) {
Object.keys(sections).forEach(key => {
if (typeof sections[key] === 'object' && sections[key] !== null && sections.hasOwnProperty(key)) {
if (sections[key].hasOwnProperty('html') && sections[key].hasOwnProperty('markdown') && Object.keys(sections[key]).length === 2) {
if (options.format === 'html') {
delete sections[key]['markdown']
} else {
delete sections[key]['html']
}
} else {
formatContent(sections[key])
}
}
})
}
}

formatContent(sections_data)
format_markdown_content(sections_data, options.format)

const sections = sections_data?.map(section => ({
const sections = sections_data?.map((section) => ({
// @ts-ignore
...section.symbol['content'][lang], // static field values
// @ts-ignore
Expand All @@ -95,14 +121,47 @@ export async function GET({ url, params }) {
id: section.id,
symbol: section.symbol.id,
name: section.symbol.name,
created_at: section.created_at
}
created_at: section.created_at,
},
}))

console.log({ sections_data })

return json({
site,
page,
sections
sections,
})
}

}
/**
* Removes html or markdown from Markdown field content
* @param {Array<import('$lib').Section> | import('$lib').Section} sections
* @param {string} format */
function format_markdown_content(sections, format) {
if (Array.isArray(sections)) {
sections.forEach((item) => format_markdown_content(item, format))
} else if (typeof sections === 'object' && sections !== null) {
Object.keys(sections).forEach((key) => {
if (
typeof sections[key] === 'object' &&
sections[key] !== null &&
sections.hasOwnProperty(key)
) {
if (
sections[key].hasOwnProperty('html') &&
sections[key].hasOwnProperty('markdown') &&
Object.keys(sections[key]).length === 2
) {
if (format) {
delete sections[key]['markdown']
} else {
delete sections[key]['html']
}
} else {
format_markdown_content(sections[key], format)
}
}
})
}
}
Loading

0 comments on commit 041c77b

Please sign in to comment.