mirror of
https://github.com/supabase/supabase.git
synced 2026-10-18 23:59:08 +03:00
Merge branch 'feat/pricing-updates' of github.com:supabase/supabase into feat/pricing-updates
This commit is contained in:
commit
fc4f4ce3b7
13 files changed
+281
-234
No files matched your search
@@ -13,15 +13,14 @@ date: '2022-03-08'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
|
||||
Data auditing is a system that tracks changes to tables' contents over time.
|
||||
Data auditing is a system that tracks changes to tables' contents over time.
|
||||
PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL.
|
||||
|
||||
Auditing is particularly useful for historical analysis. To demonstrate, imagine you have a `users` table that tracks when a user is online.
|
||||
You might add a `status` column which can have one of two values: `online` and `offline`. How would you track how long a user is online for throughout an entire month?
|
||||
Auditing is particularly useful for historical analysis. To demonstrate, imagine you have a `users` table that tracks when a user is online.
|
||||
You might add a `status` column which can have one of two values: `online` and `offline`. How would you track how long a user is online for throughout an entire month?
|
||||
An auditing system would track every change with timestamps, and so you can measure the difference between each timestamp and sum them up for the entire month.
|
||||
|
||||
The goals of our auditing solution are:
|
||||
The goals of our auditing solution are:
|
||||
|
||||
- low maintenance
|
||||
- easy to use
|
||||
@@ -60,11 +59,11 @@ select * from audit.record_history
|
||||
```
|
||||
|
||||
```markdown
|
||||
id | record_id | old_record_id | op | ts | table_oid | table_schema | table_name | record | old_record
|
||||
id | record_id | old_record_id | op | ts | table_oid | table_schema | table_name | record | old_record
|
||||
----+--------------------------------------+--------------------------------------+--------+-------------------------------------+-----------+--------------+------------+--------------------------+--------------------------
|
||||
2 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | | INSERT | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | {"id": 1, "name": "foo"} |
|
||||
3 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | UPDATE | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | {"id": 1, "name": "bar"} | {"id": 1, "name": "foo"}
|
||||
4 | | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | DELETE | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | | {"id": 1, "name": "bar"}
|
||||
2 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | | INSERT | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | {"id": 1, "name": "foo"} |
|
||||
3 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | UPDATE | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | {"id": 1, "name": "bar"} | {"id": 1, "name": "foo"}
|
||||
4 | | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | DELETE | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | | {"id": 1, "name": "bar"}
|
||||
(3 rows)
|
||||
```
|
||||
|
||||
@@ -74,10 +73,9 @@ Notice that our `record_id` and `old_record_id` stayed constant as we updated th
|
||||
|
||||
### Namespace
|
||||
|
||||
To quote a tenet from [the zen of python](https://www.python.org/dev/peps/pep-0020/):
|
||||
To quote a tenet from [the zen of python](https://www.python.org/dev/peps/pep-0020/):
|
||||
|
||||
> Namespaces are one honking great idea -- let's do more of those!
|
||||
>
|
||||
|
||||
So first things first, we'll create a separate schema named `audit` to house our auditing entities.
|
||||
|
||||
@@ -89,12 +87,12 @@ create schema if not exists audit;
|
||||
|
||||
Next, we need a table to track inserts, updates and deletes.
|
||||
|
||||
Classically, an audit table's schema mirrors the table being audited and appends some metadata columns like the commit's timestamp. That solution has a few maintenance challenges:
|
||||
Classically, an audit table's schema mirrors the table being audited and appends some metadata columns like the commit's timestamp. That solution has a few maintenance challenges:
|
||||
|
||||
- enabling auditing on a table requires a database migration
|
||||
- when the source table's schema changes, the audit table's schema must also change
|
||||
|
||||
So instead, we'll lean on PostgreSQL's schema-less [`JSONB` data type](https://www.postgresql.org/docs/current/datatype-json.html) to store each record's data in a single column.
|
||||
So instead, we'll lean on PostgreSQL's schema-less [`JSONB` data type](https://www.postgresql.org/docs/current/datatype-json.html) to store each record's data in a single column.
|
||||
That approach has the added benefit of allowing us to store multiple tables' audit history in a single audit table.
|
||||
|
||||
```sql
|
||||
@@ -110,23 +108,22 @@ create table audit.record_version(
|
||||
table_schema name not null, -- audited table's schema name e.g. 'public'
|
||||
table_name name not null, -- audited table's table name e.g. 'account'
|
||||
-- record data
|
||||
record jsonb, -- contents of the new record
|
||||
record jsonb, -- contents of the new record
|
||||
old_record jsonb -- previous record contents (for UPDATE/DELETE)
|
||||
);
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Postgres version compatibility</summary>
|
||||
<p>
|
||||
The table above uses PostgreSQL's built-in <a href="https://www.postgresql.org/docs/14/functions-uuid.html">uuid functionality</a>, which is available from version 14.
|
||||
For backwards compatibility you can use the uuid-ossp extension.
|
||||
</p>
|
||||
<p>
|
||||
<code>create extension if not exists "uuid-ossp";</code>
|
||||
</p>
|
||||
<summary>Postgres version compatibility</summary>
|
||||
<p>
|
||||
The table above uses PostgreSQL's built-in{' '}
|
||||
<a href="https://www.postgresql.org/docs/14/functions-uuid.html">uuid functionality</a>, which
|
||||
is available from version 14. For backwards compatibility you can use the uuid-ossp extension.
|
||||
</p>
|
||||
<p>
|
||||
<code>create extension if not exists "uuid-ossp";</code>
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
### Query Patterns
|
||||
|
||||
@@ -134,7 +131,7 @@ An audit log doesn't do us much good if its too slow to query! There are 2 query
|
||||
|
||||
**Changes to a Table in a Time Range**
|
||||
|
||||
For time slices, we need an index on the `ts` column. Since the table is append-only and the `ts` column is populated by insertion date, our values for `ts` are naturally in ascending order.
|
||||
For time slices, we need an index on the `ts` column. Since the table is append-only and the `ts` column is populated by insertion date, our values for `ts` are naturally in ascending order.
|
||||
|
||||
PostgreSQL's builtin [BRIN index](https://www.postgresql.org/docs/current/brin-intro.html) can leverage that correlation between value and physical location to produce an index that, at scale, is many hundreds of times smaller than the default (BTREE index) with faster lookup times.
|
||||
|
||||
@@ -213,7 +210,7 @@ as $$
|
||||
select
|
||||
uuid_generate_v5(
|
||||
'fd62bc3d-8d6e-43c2-919c-802ba3762271',
|
||||
(
|
||||
(
|
||||
jsonb_build_array(to_jsonb($1))
|
||||
|| jsonb_agg($3 ->> key_)
|
||||
)::text
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Space } from '@supabase/ui'
|
||||
import authors from 'lib/authors.json'
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
@@ -10,7 +9,20 @@ interface Props {
|
||||
|
||||
const BlogListItem = ({ post }: Props) => {
|
||||
// @ts-ignore
|
||||
const author = post.author ? authors[post.author] : authors['supabase']
|
||||
const authorArray = post.author && post.author.split(',')
|
||||
|
||||
const author = []
|
||||
if (post.author) {
|
||||
for (let i = 0; i < authorArray.length; i++) {
|
||||
author.push(
|
||||
// @ts-ignore
|
||||
authors.find((authors: string) => {
|
||||
// @ts-ignore
|
||||
return authors.author_id === authorArray[i]
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={post.slug}>
|
||||
@@ -39,17 +51,19 @@ const BlogListItem = ({ post }: Props) => {
|
||||
{post.date && <p className="text-xs text-scale-1100">{post.date}</p>}
|
||||
<p className="text-base text-scale-1100 max-w-sm">{post.description}</p>
|
||||
</div>
|
||||
{author && !post.hideAuthor ? (
|
||||
<div className="flex items-center">
|
||||
{author.author_image_url && (
|
||||
<img src={author.author_image_url} className="rounded-full w-10 mr-4" />
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm m-0 text-scale-1200">{author.author}</span>
|
||||
<span className="text-xs m-0 text-scale-900">{author.position}</span>
|
||||
{author.map((author: any) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{author.author_image_url && (
|
||||
<img src={author.author_image_url} className="rounded-full w-10 mr-4" />
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm m-0 text-scale-1200">{author.author}</span>
|
||||
<span className="text-xs m-0 text-scale-900">{author.position}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -21,7 +21,7 @@ function ExampleCard(props: any) {
|
||||
<p className="p text-sm">{props.description}</p>
|
||||
<div>
|
||||
<img src={props.author_img} className="inline w-6 rounded-full border-scale-500" />
|
||||
<span className="text-sm ml-2">{props.author}</span>
|
||||
<span className="text-sm ml-2 text-scale-1200">{props.author}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ function FeatureColumn({ icon, title, text }: any) {
|
||||
return (
|
||||
<>
|
||||
{icon && <div className="mb-2">{icon}</div>}
|
||||
<h4 className="text-base mb-4">{title}</h4>
|
||||
<h4 className="text-base text-scale-1200 mb-4">{title}</h4>
|
||||
<p className="p">{text}</p>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -125,7 +125,7 @@ const Footer = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-32 border-t border-scale-500 dark:border-scale-600 pt-8 flex justify-between">
|
||||
<p className="mb-0 self-center text-base">© Supabase Inc</p>
|
||||
<small className="small">© Supabase Inc</small>
|
||||
<DarkModeToggle />
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
@@ -23,18 +23,20 @@ const Hero = () => {
|
||||
<h1
|
||||
className="
|
||||
sm:leading-none
|
||||
text-xl lg:text-4xl xl:text-5xl"
|
||||
text-xl lg:text-4xl xl:text-5xl
|
||||
text-scale-1200
|
||||
"
|
||||
>
|
||||
<span className="block">The Open Source</span>
|
||||
<span className="text-brand-900 md:ml-0 block">Firebase Alternative</span>
|
||||
</h1>
|
||||
<div>
|
||||
<p className="text-base sm:mt-5 lg:text-lg ">
|
||||
<p className="p text-base sm:mt-5 lg:text-lg ">
|
||||
Create a backend in less than 2 minutes. Start your project with a
|
||||
Postgres Database, Authentication, instant APIs, Realtime subscriptions
|
||||
and Storage.
|
||||
</p>
|
||||
<p className="text-base">Serverless functions coming soon</p>
|
||||
<p className="p text-base">Serverless functions coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
@@ -50,7 +52,7 @@ const Hero = () => {
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<small className="text-xs">backed by</small>
|
||||
<small className="small text-xs">backed by</small>
|
||||
<div className="w-full sm:max-w-lg lg:ml-0">
|
||||
<div className="flex flex-wrap items-center justify-start">
|
||||
<img
|
||||
|
||||
@@ -12,32 +12,28 @@ type Props = {
|
||||
|
||||
const Developers = () => {
|
||||
const { basePath } = useRouter()
|
||||
|
||||
const iconSections = Object.values(DevelopersData).map((company: Props) => {
|
||||
const { text, description, url, icon } = company
|
||||
|
||||
const content = (
|
||||
<a
|
||||
href={url}
|
||||
className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 dark:hover:bg-scale-500 transition ease-in-out duration-150"
|
||||
>
|
||||
<div className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 dark:hover:bg-scale-500 transition ease-in-out duration-150">
|
||||
{/* <!-- Heroicon name: support --> */}
|
||||
<svg
|
||||
className="flex-shrink-0 h-6 w-6 stroke-gray dark:stroke-white"
|
||||
className="flex-shrink-0 h-5 w-5 stroke-scale-900"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={icon} />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d={icon} />
|
||||
</svg>
|
||||
<div className="ml-4">
|
||||
<h5>{text}</h5>
|
||||
<p>
|
||||
<p>{description}</p>
|
||||
</p>
|
||||
<h5 className="text-base text-scale-1200">{text}</h5>
|
||||
<p className="text-sm text-scale-900">{description}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
return url ? (
|
||||
<a
|
||||
@@ -59,18 +55,18 @@ const Developers = () => {
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12">
|
||||
<nav className="col-span-6" aria-labelledby="solutionsHeading">
|
||||
<div className="grid grid-cols-12 m-3 gap-3">{iconSections}</div>
|
||||
<nav className="col-span-6 py-8" aria-labelledby="solutionsHeading">
|
||||
<div className="grid grid-cols-12 m-3 gap-x-8 gap-y-4 py-4 pr-3">{iconSections}</div>
|
||||
</nav>
|
||||
<div className="col-span-6">
|
||||
<div className="m-3 mx-6">
|
||||
<p>Latest announcements</p>
|
||||
<p className="p">Latest announcements</p>
|
||||
<ul className="mt-6 space-y-3 pb-6">
|
||||
{AnnouncementsData.map((caseStudy: any, idx: number) => (
|
||||
<li className="flow-root" key={`flyout_case_${idx}`}>
|
||||
<a
|
||||
href={caseStudy.url}
|
||||
className="p-3 flex rounded-lg hover:bg-gray-100 dark:hover:bg-dark-700 transition ease-in-out duration-150 border dark:border-gray-600 items-center"
|
||||
className="p-3 flex rounded-lg hover:bg-gray-100 dark:hover:bg-dark-700 transition ease-in-out duration-150 border items-center"
|
||||
>
|
||||
<div className="hidden sm:block flex-shrink-0">
|
||||
<img
|
||||
@@ -79,9 +75,11 @@ const Developers = () => {
|
||||
alt="caseStudyThumb"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 sm:ml-8">
|
||||
<h1 className="mb-0 text-normal">{caseStudy.title}</h1>
|
||||
<p className="text-sm">{caseStudy.description}</p>
|
||||
<div className="min-w-0 flex-1 sm:ml-4">
|
||||
<h4 className="text-base text-scale-1200 mb-0 text-normal">
|
||||
{caseStudy.title}
|
||||
</h4>
|
||||
<p className="p text-sm">{caseStudy.description}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -20,7 +20,7 @@ const Product = () => {
|
||||
<div className="ml-4 md:flex-1 md:flex md:flex-col md:justify-between lg:ml-0 mt-3">
|
||||
<p className="text-sm text-scale-1000">{description}</p>
|
||||
{label && (
|
||||
<div>
|
||||
<div className="mt-2">
|
||||
<Badge>{label}</Badge>
|
||||
</div>
|
||||
)}
|
||||
@@ -47,7 +47,7 @@ const Product = () => {
|
||||
</nav>
|
||||
<div className="col-span-6">
|
||||
<div className="m-3 mx-6">
|
||||
<p>Latest case studies</p>
|
||||
<p className="p">Latest case studies</p>
|
||||
<ul className="mt-6 space-y-3">
|
||||
{CaseStudiesData.map((caseStudy: any, idx: number) => {
|
||||
if (idx > 1) {
|
||||
@@ -57,17 +57,7 @@ const Product = () => {
|
||||
<li className="flow-root" key={`flyout_case_${idx}`}>
|
||||
<a
|
||||
href={caseStudy.url}
|
||||
className="p-3 flex rounded-lg
|
||||
|
||||
transition ease-in-out duration-150
|
||||
|
||||
hover:bg-scale-400
|
||||
dark:hover:bg-scale-500
|
||||
|
||||
border
|
||||
dark:border-scale-400
|
||||
|
||||
items-center"
|
||||
className="p-3 flex rounded-lg hover:bg-gray-100 dark:hover:bg-dark-700 transition ease-in-out duration-150 border items-center"
|
||||
>
|
||||
<div className="hidden sm:block flex-shrink-0">
|
||||
<img
|
||||
@@ -76,9 +66,11 @@ const Product = () => {
|
||||
alt="caseStudyThumb"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 sm:ml-8">
|
||||
<h1 className="mb-0 text-normal">{caseStudy.title}</h1>
|
||||
<p className="text-sm">{caseStudy.description}</p>
|
||||
<div className="min-w-0 flex-1 sm:ml-4">
|
||||
<h4 className="text-base text-scale-1200 mb-0 text-normal">
|
||||
{caseStudy.title}
|
||||
</h4>
|
||||
<p className="p text-sm">{caseStudy.description}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -19,7 +19,7 @@ const ProductHeader = (props: Types) => (
|
||||
<div className="col-span-12 lg:col-span-5 space-y-8">
|
||||
<div>
|
||||
{props.icon || props.title ? (
|
||||
<div className="flex mb-4 items-center gap-2">
|
||||
<div className="flex mb-4 items-center gap-3">
|
||||
{props.icon && <ProductIcon icon={props.icon} />}
|
||||
{props.title && (
|
||||
<span className="text-scale-1200" key={`product-name-${props.title}`}>
|
||||
|
||||
@@ -7,7 +7,7 @@ const SectionHeader = (props: any) => {
|
||||
</span>
|
||||
<h3 className="h2">
|
||||
<span>{props.title}</span>
|
||||
{props.title_alt && <span className="text-scale-900 inline">{props.title_alt}</span>}
|
||||
{props.title_alt && <span className="text-scale-1100 inline">{props.title_alt}</span>}
|
||||
</h3>
|
||||
</div>
|
||||
{props.paragraph && <p className="p text-lg sm:mt-4 max-w-3xl">{props.paragraph}</p>}
|
||||
|
||||
+94
-79
@@ -1,101 +1,116 @@
|
||||
{
|
||||
"rory_wilding": {
|
||||
"author": "Rory Wilding",
|
||||
"author_url": "https://github.com/roryw10",
|
||||
"author_image_url": "https://github.com/roryw10.png",
|
||||
"position": "Head of Growth"
|
||||
},
|
||||
"ant_wilson": {
|
||||
"author": "Ant Wilson",
|
||||
"author_url": "https://github.com/awalias",
|
||||
"author_image_url": "https://github.com/awalias.png",
|
||||
"position": "CTO and Co-Founder"
|
||||
},
|
||||
"paul_copplestone": {
|
||||
"author": "Paul Copplestone",
|
||||
"author_url": "https://github.com/kiwicopple",
|
||||
"author_image_url": "https://github.com/kiwicopple.png",
|
||||
"position": "CEO and Co-Founder"
|
||||
},
|
||||
"angelico_de_los_reyes": {
|
||||
[
|
||||
{
|
||||
"author_id": "angelico_de_los_reyes",
|
||||
"author": "Angelico de los Reyes",
|
||||
"position": "",
|
||||
"author_url": "https://github.com/dragarcia",
|
||||
"author_image_url": "https://avatars0.githubusercontent.com/u/26374889?s=400&u=f5e35e9b47a50fa2b4d8c4bb96babd921071bcf1&v=4",
|
||||
"position": ""
|
||||
"author_image_url": "https://github.com/dragarcia.png"
|
||||
},
|
||||
"thor_schaeff": {
|
||||
"author": "Thor Schaeff",
|
||||
"author_url": "https://github.com/thorwebdev",
|
||||
"author_image_url": "https://github.com/thorwebdev.png",
|
||||
"position": "DevRel & DX"
|
||||
{
|
||||
"author_id": "ant_wilson",
|
||||
"author": "Ant Wilson",
|
||||
"position": "CTO and Co-Founder",
|
||||
"author_url": "https://github.com/awalias",
|
||||
"author_image_url": "https://github.com/awalias.png"
|
||||
},
|
||||
"supabase": {
|
||||
"author": "Supabase",
|
||||
"author_url": "https://github.com/supabase",
|
||||
"author_image_url": "https://github.com/supabase.png",
|
||||
"position": ""
|
||||
},
|
||||
"inian": {
|
||||
"author": "Inian Parameshwaran",
|
||||
"author_url": "https://twitter.com/everConfusedGuy",
|
||||
"position": "Engineering",
|
||||
"username": "inian",
|
||||
"author_image_url": "https://avatars.githubusercontent.com/u/2155545?v=4"
|
||||
},
|
||||
"soedirgo": {
|
||||
{
|
||||
"author_id": "soedirgo",
|
||||
"author": "Bobbie Soedirgo",
|
||||
"author_url": "https://github.com/soedirgo",
|
||||
"position": "Engineering",
|
||||
"username": "soedirgo",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/soedirgo",
|
||||
"author_image_url": "https://github.com/soedirgo.png"
|
||||
},
|
||||
"fracek": {
|
||||
"author": "Francesco Ceccon",
|
||||
"author_url": "https://github.com/fracek",
|
||||
"position": "Engineering",
|
||||
"username": "fracek",
|
||||
"author_image_url": "https://github.com/fracek.png"
|
||||
},
|
||||
"steve_chavez": {
|
||||
"author": "Steve Chavez",
|
||||
"author_url": "https://github.com/steve-chavez",
|
||||
"position": "Engineering & PostgREST maintainer",
|
||||
"username": "steve-chavez",
|
||||
"author_image_url": "https://github.com/steve-chavez.png"
|
||||
},
|
||||
"kangmingtay": {
|
||||
"author": "Kang Ming Tay",
|
||||
"author_url": "https://github.com/kangmingtay",
|
||||
"position": "Engineering",
|
||||
"username": "kangmingtay",
|
||||
"author_image_url": "https://github.com/kangmingtay.png"
|
||||
},
|
||||
"div_arora": {
|
||||
{
|
||||
"author_id": "div_arora",
|
||||
"author": "Div Arora",
|
||||
"author_url": "https://github.com/darora",
|
||||
"position": "Engineering",
|
||||
"username": "darora",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/darora",
|
||||
"author_image_url": "https://github.com/darora.png"
|
||||
},
|
||||
"gurjeet": {
|
||||
"author": "Gurjeet Singh",
|
||||
"author_url": "https://github.com/gurjeet",
|
||||
{
|
||||
"author_id": "fracek",
|
||||
"author": "Francesco Ceccon",
|
||||
"username": "fracek",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/fracek",
|
||||
"author_image_url": "https://github.com/fracek.png"
|
||||
},
|
||||
{
|
||||
"author_id": "gurjeet",
|
||||
"author": "Gurjeet Singh",
|
||||
"username": "gurjeet",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/gurjeet",
|
||||
"author_image_url": "https://github.com/gurjeet.png"
|
||||
},
|
||||
"wenbo_xie": {
|
||||
"author": "Wen Bo Xie",
|
||||
"author_url": "https://github.com/w3b6x9",
|
||||
{
|
||||
"author_id": "inian",
|
||||
"author": "Inian Parameshwaran",
|
||||
"username": "inian",
|
||||
"position": "Engineering",
|
||||
"username": "w3b6x9",
|
||||
"author_image_url": "https://github.com/w3b6x9.png"
|
||||
"author_url": "https://twitter.com/everConfusedGuy",
|
||||
"author_image_url": "https://avatars.githubusercontent.com/u/2155545?v=4"
|
||||
},
|
||||
"oli_rice": {
|
||||
"author": "Oliver Rice",
|
||||
"author_url": "https://github.com/olirice",
|
||||
{
|
||||
"author_id": "kangmingtay",
|
||||
"author": "Kang Ming Tay",
|
||||
"username": "kangmingtay",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/kangmingtay",
|
||||
"author_image_url": "https://github.com/kangmingtay.png"
|
||||
},
|
||||
{
|
||||
"author_id": "oli_rice",
|
||||
"author": "Oliver Rice",
|
||||
"username": "olirice",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/olirice",
|
||||
"author_image_url": "https://github.com/olirice.png"
|
||||
},
|
||||
{
|
||||
"author_id": "paul_copplestone",
|
||||
"author": "Paul Copplestone",
|
||||
"position": "CEO and Co-Founder",
|
||||
"author_url": "https://github.com/kiwicopple",
|
||||
"author_image_url": "https://github.com/kiwicopple.png"
|
||||
},
|
||||
{
|
||||
"author_id": "rory_wilding",
|
||||
"author": "Rory Wilding",
|
||||
"position": "Head of Growth",
|
||||
"author_url": "https://github.com/roryw10",
|
||||
"author_image_url": "https://github.com/roryw10.png"
|
||||
},
|
||||
{
|
||||
"author_id": "steve_chavez",
|
||||
"author": "Steve Chavez",
|
||||
"username": "steve-chavez",
|
||||
"position": "Engineering & PostgREST maintainer",
|
||||
"author_url": "https://github.com/steve-chavez",
|
||||
"author_image_url": "https://github.com/steve-chavez.png"
|
||||
},
|
||||
{
|
||||
"author_id": "supabase",
|
||||
"author": "Supabase",
|
||||
"position": "",
|
||||
"author_url": "https://github.com/supabase",
|
||||
"author_image_url": "https://github.com/supabase.png"
|
||||
},
|
||||
{
|
||||
"author_id": "thor_schaeff",
|
||||
"author": "Thor Schaeff",
|
||||
"position": "DevRel & DX",
|
||||
"author_url": "https://github.com/thorwebdev",
|
||||
"author_image_url": "https://github.com/thorwebdev.png"
|
||||
},
|
||||
{
|
||||
"author_id": "wenbo_xie",
|
||||
"author": "Wen Bo Xie",
|
||||
"username": "w3b6x9",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/w3b6x9",
|
||||
"author_image_url": "https://github.com/w3b6x9.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
+34
-23
@@ -132,13 +132,24 @@ function Blog(props: any) {
|
||||
|
||||
function FeaturedThumb(blog: PostTypes) {
|
||||
// @ts-ignore
|
||||
const author = blog.author ? authors[blog.author] : authors['supabase']
|
||||
const authorArray = blog.author.split(',')
|
||||
|
||||
const author = []
|
||||
for (let i = 0; i < authorArray.length; i++) {
|
||||
// @ts-ignore
|
||||
author.push(
|
||||
authors.find((authors: string) => {
|
||||
// @ts-ignore
|
||||
return authors.author_id === authorArray[i]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={blog.slug} className="cursor-pointer w-full">
|
||||
<a href={`/blog/${blog.url}`}>
|
||||
<a className="grid lg:grid-cols-2 gap-8 lg:gap-16">
|
||||
<div className="relative overflow-auto w-full h-96 border dark:border-dark rounded-lg">
|
||||
<div className="relative overflow-auto w-full h-96 border rounded-lg">
|
||||
<Image
|
||||
src={`/images/blog/` + (blog.thumb ? blog.thumb : blog.image)}
|
||||
layout="fill"
|
||||
@@ -146,37 +157,37 @@ function FeaturedThumb(blog: PostTypes) {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div className="flex space-x-2 text-sm">
|
||||
<div className="text-scale-900 flex space-x-2 text-sm">
|
||||
<p>{blog.date}</p>
|
||||
<p>•</p>
|
||||
<p>{blog.readingTime}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-4xl">{blog.title}</h2>
|
||||
<p className="m-0">
|
||||
<span className="text-xl">{blog.description}</span>
|
||||
</p>
|
||||
<h2 className="h2">{blog.title}</h2>
|
||||
<p className="p text-xl">{blog.description}</p>
|
||||
</div>
|
||||
|
||||
{author && (
|
||||
<div className="flex space-x-3 items-center">
|
||||
{author.author_image_url && (
|
||||
<div className="relative overflow-auto w-10 h-10">
|
||||
<Image
|
||||
src={author.author_image_url}
|
||||
className="rounded-full"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
{author.map((author: any) => {
|
||||
return (
|
||||
<div className="flex space-x-3 items-center">
|
||||
{author.author_image_url && (
|
||||
<div className="relative overflow-auto w-10 h-10">
|
||||
<Image
|
||||
src={author.author_image_url}
|
||||
className="rounded-full"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="m-0 text-sm text-scale-1200">{author.author}</span>
|
||||
<span className="m-0 text-xs text-scale-900">{author.position}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="m-0 text-sm text-scale-1200">{author.author}</span>
|
||||
<span className="m-0 text-xs text-scale-900">{author.position}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</a>
|
||||
</a>
|
||||
|
||||
@@ -88,8 +88,19 @@ export async function getStaticProps({ params }: any) {
|
||||
|
||||
function BlogPostPage(props: any) {
|
||||
// @ts-ignore
|
||||
const author = props.blog.author ? authors[props.blog.author] : authors['supabase']
|
||||
const content = hydrate(props.blog.content, { components })
|
||||
const authorArray = props.blog.author.split(',')
|
||||
|
||||
const author = []
|
||||
for (let i = 0; i < authorArray.length; i++) {
|
||||
author.push(
|
||||
// @ts-ignore
|
||||
authors.find((authors: string) => {
|
||||
// @ts-ignore
|
||||
return authors.author_id === authorArray[i]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const { basePath } = useRouter()
|
||||
|
||||
@@ -102,22 +113,17 @@ function BlogPostPage(props: any) {
|
||||
return (
|
||||
<Link href={`/blog/${post.url}`} as={`/blog/${post.url}`}>
|
||||
<div className={className}>
|
||||
<Card className="cursor-pointer" hoverable>
|
||||
<Space direction="vertical">
|
||||
<div className="cursor-pointer border border-scale-500 p-6 transition rounded hover:bg-scale-100 dark:hover:bg-scale-300">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p>{label}</p>
|
||||
<p className="text-sm text-scale-900">{label}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{post.title}</h4>
|
||||
<p>{post.date}</p>
|
||||
<h4 className="text-lg text-scale-1200">{post.title}</h4>
|
||||
<p className="small">{post.date}</p>
|
||||
</div>
|
||||
<div>
|
||||
{post.tags.map((tag: string) => {
|
||||
return <Badge key={`category-badge-${tag}`}>{tag}</Badge>
|
||||
})}
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
@@ -191,7 +197,7 @@ function BlogPostPage(props: any) {
|
||||
<p>
|
||||
<a
|
||||
href={'/blog'}
|
||||
className="hover:text-gray-900 dark:hover:text-white cursor-pointer flex items-center"
|
||||
className="transition text-sm text-scale-900 hover:text-scale-1200 cursor-pointer flex items-center"
|
||||
>
|
||||
<IconChevronLeft style={{ padding: 0 }} />
|
||||
Back
|
||||
@@ -203,44 +209,52 @@ function BlogPostPage(props: any) {
|
||||
<div className="mb-16 space-y-8 max-w-5xl">
|
||||
<div className="space-y-4">
|
||||
<p className="text-brand-900">Blog post</p>
|
||||
<h1 className="text-5xl">{props.blog.title}</h1>
|
||||
<div className="flex space-x-3 text-sm">
|
||||
<h1 className="h1">{props.blog.title}</h1>
|
||||
<div className="text-scale-900 flex space-x-3 text-sm">
|
||||
<p>{props.blog.date}</p>
|
||||
<p>•</p>
|
||||
<p>{generateReadingTime(props.blog.content.renderedOutput)}</p>
|
||||
</div>
|
||||
{author && (
|
||||
<div className="mt-6 mb-8 lg:mb-0 w-max">
|
||||
<Link href={author.author_url}>
|
||||
<a className="cursor-pointer">
|
||||
<div className="flex items-center">
|
||||
{author.author_image_url && (
|
||||
<div className="w-10">
|
||||
<Image
|
||||
src={author.author_image_url}
|
||||
className="rounded-full border dark:border-dark"
|
||||
width="100%"
|
||||
height="100%"
|
||||
layout="responsive"
|
||||
/>
|
||||
<div className="flex">
|
||||
{author.map((author: any) => {
|
||||
return (
|
||||
<div className="mt-6 mb-8 mr-4 lg:mb-0 w-max">
|
||||
<Link href={author.author_url}>
|
||||
<a className="cursor-pointer">
|
||||
<div className="flex gap-3 items-center">
|
||||
{author.author_image_url && (
|
||||
<div className="w-10">
|
||||
<Image
|
||||
src={author.author_image_url}
|
||||
className="rounded-full border dark:border-dark"
|
||||
width="100%"
|
||||
height="100%"
|
||||
layout="responsive"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm text-scale-1200 mb-0">
|
||||
{author.author}
|
||||
</span>
|
||||
<span className="text-xs mb-0 text-scale-900">
|
||||
{author.position}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs mb-0">{author.author}</span>
|
||||
<span className="text-xs mb-0 text-scale-900">{author.position}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 lg:gap-16 xl:gap-8">
|
||||
{/* Content */}
|
||||
<div className="col-span-12 lg:col-span-7 xl:col-span-7">
|
||||
{props.blog.thumb && (
|
||||
<div className="relative overflow-auto w-full h-96 mb-8 border dark:border-gray-600">
|
||||
<div className="relative overflow-auto w-full h-96 mb-8 border rounded">
|
||||
<Image
|
||||
src={'/images/blog/' + props.blog.thumb}
|
||||
layout="fill"
|
||||
@@ -270,7 +284,7 @@ function BlogPostPage(props: any) {
|
||||
>
|
||||
<path
|
||||
d="m154.729 400c185.669 0 287.205-153.876 287.205-287.312 0-4.37-.089-8.72-.286-13.052a205.304 205.304 0 0 0 50.352-52.29c-18.087 8.044-37.55 13.458-57.968 15.899 20.841-12.501 36.84-32.278 44.389-55.852a202.42 202.42 0 0 1 -64.098 24.511c-18.42-19.628-44.644-31.904-73.682-31.904-55.744 0-100.948 45.222-100.948 100.965 0 7.925.887 15.631 2.619 23.025-83.895-4.223-158.287-44.405-208.074-105.504a100.739 100.739 0 0 0 -13.668 50.754c0 35.034 17.82 65.961 44.92 84.055a100.172 100.172 0 0 1 -45.716-12.63c-.015.424-.015.837-.015 1.29 0 48.903 34.794 89.734 80.982 98.986a101.036 101.036 0 0 1 -26.617 3.553c-6.493 0-12.821-.639-18.971-1.82 12.851 40.122 50.115 69.319 94.296 70.135-34.549 27.089-78.07 43.224-125.371 43.224a204.9 204.9 0 0 1 -24.078-1.399c44.674 28.645 97.72 45.359 154.734 45.359"
|
||||
fill-rule="nonzero"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
@@ -313,14 +327,16 @@ function BlogPostPage(props: any) {
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-scale-1200">Related articles</p>
|
||||
</div>
|
||||
<div className="space-y-0">
|
||||
<div className="space-y-3">
|
||||
{props.relatedPosts.map((post: any) => (
|
||||
<Link href={`/blog/${post.url}`} as={`/blog/${post.url}`}>
|
||||
<div>
|
||||
<p className="cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconFile size={'small'} style={{ minWidth: '1.2rem' }} />
|
||||
<span className="text-sm hover:text-gray-900 dark:hover:text-white">
|
||||
<div className="flex gap-2">
|
||||
<div className="text-scale-900">
|
||||
<IconFile size={'small'} style={{ minWidth: '1.2rem' }} />
|
||||
</div>
|
||||
<span className="text-sm text-scale-1100 hover:text-gray-1200">
|
||||
{post.title}
|
||||
</span>
|
||||
</div>
|
||||
@@ -329,11 +345,13 @@ function BlogPostPage(props: any) {
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<Link href={`/blog`} as={`/blog`}>
|
||||
<a className="text-sm text-scale-1100 hover:text-scale-1200 cursor-pointer">
|
||||
View all posts
|
||||
</a>
|
||||
</Link>
|
||||
<div className="mt-2">
|
||||
<Link href={`/blog`} passHref>
|
||||
<a className="text-sm text-scale-1100 hover:text-scale-1200 cursor-pointer">
|
||||
View all posts
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user