Showing preview only (308K chars total). Download the full file or copy to clipboard to get everything.
Repository: supabase-community/supabase-graphql-example
Branch: main
Commit: 504560d3124a
Files: 71
Total size: 288.9 KB
Directory structure:
gitextract_uc_3wbbe/
├── .editorconfig
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .graphqlrc.yml
├── .husky/
│ ├── .gitignore
│ └── pre-commit
├── .vscode/
│ ├── extensions.json
│ └── settings.json
├── README.md
├── app/
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── gql/
│ │ └── .gitignore
│ ├── lib/
│ │ ├── active-link.tsx
│ │ ├── comment-item.tsx
│ │ ├── container.tsx
│ │ ├── feed-item.tsx
│ │ ├── footer.tsx
│ │ ├── icons.tsx
│ │ ├── loading.tsx
│ │ ├── main-section.tsx
│ │ ├── navigation.tsx
│ │ ├── noop-uuid.ts
│ │ ├── supabase.tsx
│ │ ├── time-ago.ts
│ │ ├── urql.tsx
│ │ └── use-paginated-query.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages/
│ │ ├── _app.tsx
│ │ ├── about.tsx
│ │ ├── account.tsx
│ │ ├── api/
│ │ │ └── graphiql.ts
│ │ ├── comments.tsx
│ │ ├── index.tsx
│ │ ├── item/
│ │ │ └── [postId].tsx
│ │ ├── login.tsx
│ │ ├── logout.tsx
│ │ ├── newest.tsx
│ │ ├── profile/
│ │ │ └── [profileId].tsx
│ │ └── submit.tsx
│ ├── postcss.config.js
│ ├── styles/
│ │ └── globals.css
│ ├── tailwind.config.js
│ └── tsconfig.json
├── data/
│ ├── db/
│ │ ├── backup.sql
│ │ ├── row_level_security_polices.csv
│ │ └── schema.sql
│ ├── seed/
│ │ ├── blog.xml
│ │ ├── blog_posts.csv
│ │ └── comments.csv
│ └── supabase/
│ ├── 00-initial-schema.sql
│ ├── 01-auth-schema.sql
│ ├── 02-storage-schema.sql
│ ├── 03-post-setup.sql
│ ├── 04-public-profiles.sql
│ ├── 05-setup-total-counts.sql
│ ├── 05-setup-user-profile-trigger.sql
│ ├── 06-update-post-vote-counts.sql
│ ├── 07-add-post-title-url-constraints.sql
│ ├── 08-update-post-cascahe-delete-constraints.sql
│ ├── 09-update-all-gravatars.sql
│ └── rls-policies.md
├── graphql/
│ ├── queries/
│ │ ├── feed.graphql
│ │ ├── hasProfileVotes.graphql
│ │ └── rankedFeed.graphql
│ └── schema/
│ └── schema.graphql
├── package.json
├── renovate.json
└── scripts/
└── fetchGraphQLSchema.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .github/workflows/ci.yml
================================================
name: ci
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: the-guild-org/shared-config/setup@main
with:
nodeVersion: 16
- run: yarn codegen
- run: yarn workspace app run build
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: the-guild-org/shared-config/setup@main
with:
nodeVersion: 16
- run: yarn workspace app run lint
================================================
FILE: .gitignore
================================================
.idea
.DS_Store
.env
.env.production
yarn-error.log
node_modules
================================================
FILE: .graphqlrc.yml
================================================
schema: ./graphql/schema/schema.graphql
documents: ./app/**/*.{graphql,js,ts,jsx,tsx}
extensions:
codegen:
generates:
./app/gql:
preset: gql-tag-operations-preset
hooks:
afterOneFileWrite:
- prettier --write
================================================
FILE: .husky/.gitignore
================================================
_
================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": [
"esbenp.prettier-vscode",
"GraphQL.vscode-graphql",
"bradlc.vscode-tailwindcss"
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
================================================
FILE: README.md
================================================
# Supabase GraphQL Example
A basic HackerNews-like clone where posts can be submitted with url links and then up and down voted.
<img width="1000" alt="graphql-hn" src="https://user-images.githubusercontent.com/10214025/160611420-29705df8-3e0a-471e-baef-04a3e2ac5618.png">
- Example: [supabase-graphql-example.vercel.app](https://supabase-graphql-example.vercel.app/)
- Features: [supabase-graphql-example.vercel.app/about](https://supabase-graphql-example.vercel.app/about)
## Showcase
### Backend
- CRUD (Query + Mutation Operations)
- Cursor Based Pagination
- Authorization / Postgres Row Level Security
- [Supabase](https://supabase.com) - Create a backend in less than 2 minutes. Start your project with a Postgres Database, Authentication, instant APIs, Realtime subscriptions and Storage.
- [pg_graphql](https://supabase.com/blog/2021/12/03/pg-graphql) - A native [PostgreSQL extension](https://supabase.github.io/pg_graphql/) adding [GraphQL support](https://graphql.org). The extension keeps schema generation, query parsing, and resolvers all neatly contained on your database server requiring no external services.
- [Postgres Triggers](https://supabase.com/blog/2021/07/30/supabase-functions-updates) and [Postgres Functions](https://supabase.com/docs/guides/database/functions) - When votes are in, use triggers to invoke a Postgres function that calculates a post score to rank the feed
- [Postgres Enumerated Types](https://www.postgresql.org/docs/14/datatype-enum.html) - Enums help defined the direction of a vote: UP or DOWN.
### Frontend
- [Next.js](https://nextjs.org) - React Framework
- [TypeScript](https://www.typescriptlang.org) - TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.
- [graphql-code-generator](https://www.graphql-code-generator.com) - Generate code from your GraphQL schema and operations with a simple CLI
- [gql-tag-operations-preset](https://www.graphql-code-generator.com/plugins/gql-tag-operations-preset) - This code gen preset generates typings for your inline gql function usages, without having to manually specify import statements for the documents
- [urql](https://formidable.com/open-source/urql/) - A highly customizable and versatile GraphQL client
- [Gravatar](https://en.gravatar.com) - Default avatar profile images from Gravatar
### Functionality
- Registration
- Get a ranked feed of posts
- Create Post
- Delete Post
- Create Comment
- Delete Comment
- Upvote/Downvote Post
- View Profile (Account)
- View Profile (Public)
- Pagination (Posts, Comments)
## QuickStart
### Setup env vars
- `cp app/.env.example app/.env`
- Fill in your url and anon key from the Supabase Dashboard: https://app.supabase.io/project/_/settings/api
### Install dependencies, GraphQL codegen, run app
```bash
yarn
yarn codegen
yarn workspace app dev
```
### Deploy to Vercel
Provide the following settings to deploy a production build to Vercel:
- BUILD COMMAND: `yarn codegen && yarn workspace app build`
- OUTPUT DIRECTORY: `./app/.next`
- INSTALL COMMAND: `yarn`
- DEVELOPMENT COMMAND: `yarn codegen && yarn workspace app dev --port $PORT`
## Development
1. Fetch latest GraphQL Schema
```bash
yarn codegen:fetch
```
2. Generate Types and Watch for Changes
```bash
yarn codegen:watch
```
3. Run server
```bash
yarn workspace app dev
```
### Synchronize the GraphQL schema
Note: You need to call `select graphql.rebuild_schema()` manually to synchronize the GraphQL schema with the SQL schema after altering the SQL schema.
#### Manage Schema with dbmate
1. `brew install dbmate`
2. Setup `.env` with `DATABASE_URL`
3. Dump Schema
```
cd data
dbmate dump
```
> Note: If `pgdump` fails due to row locks, a workaround is to grant the `postgres` role superuser permissions with `ALTER USER postgres WITH SUPERUSER`. After dumping the schema, you should reset the permissions using `ALTER USER postgres WITH NOSUPERUSER`. You can run these statements in the Superbase Dashboard SQL Editors.
## Schema (Public)
- Profile belongs to auth.users
- Post
- Comment belongs to Post and Profile
- Vote belongs to Post (can have a direction of UP/DOWN)
- direction enum is "UP" or "DOWN"
### Constraints
- Post `url` is unique
- Vote is unique per Profile, Post (ie, you cannot vote more than once -- up or down)
See: [`./data/db/schema.sql`](./data/db/schema.sql)
> Note: The schema includes the entire Supabase schema with auth, storage, functions, etc.
## Seed Data
A data file for all Supabase Blog posts from the RSS feed can be found in `./data/seed/blog_posts.csv` and can be loaded. Another file for `comments` is available as well.
Note: Assumes a known `profileId` currently.
## GraphQL Schema
See: [`./graphql/schema/schema.graphql`](./graphql/schema/schema.graphql)
## Example Query
See: [`./graphql/queries/`](./graphql/queries/)
Use: `https://mvrfvzcivgabojxddwtk.supabase.co/graphql/v1`
> Note: Needs headers
```
Content-Type: application/json
apiKey: <supabase_anon_key>
```
## GraphiQL
GraphiQL is an in-browser IDE for writing, validating, and testing GraphQL queries.
Visit `http://localhost:3000/api/graphiql` for the [Yoga GraphiQL Playground](https://www.graphql-yoga.com/docs/features/graphiql) where you can experiment with queries and mutations.
> Note: Needs headers
```
Content-Type: application/json
apiKey: <supabase_anon_key>
```
> Note: In order for the RLS policies authenticate you, you have to pass an authorization header ([see example](https://github.com/supabase-community/supabase-graphql-example/blob/main/app/lib/urql.tsx#L15)):
```
authorization: Bearer <access_token>
```
### Ranked Feed
```gql
query {
rankedFeed: postCollection(orderBy: [{ voteRank: AscNullsFirst }]) {
edges {
post: node {
id
title
url
upVoteTotal
downVoteTotal
voteTotal
voteDelta
score
voteRank
comments: commentCollection {
edges {
node {
id
message
profile {
id
username
avatarUrl
}
}
}
commentCount: totalCount
}
}
}
}
}
```
# Row Level Security Matrix (RLS)
You can query all policies via: `select * from pg_policies`.
See: [Row Level Security Matrix (RLS)](./data/supabase/rls-policies.md)
## Read More
- [pg_graphql](https://supabase.github.io/pg_graphql)
- [pg_graphql Configuration](https://supabase.github.io/pg_graphql/configuration)
## Troubleshooting
1. `dbmate` can create `schema_migrations` tables in schemas. To make sure they are not included in your GraphQL Schema:
```sql
revoke select on table public.schema_migrations from anon, authenticated;
```
2. To [enable inflection](https://supabase.github.io/pg_graphql/configuration/#inflection)
```sql
comment on schema public is e'@graphql({"inflect_names": true})';
```
3. Try the heartbeat to see if pg_graphql can access requests
```
select graphql_public.graphql(
null,
$$ { heartbeat }$$
)
```
Returns:
```json
{ "data": { "heartbeat": "2022-07-28T17:07:07.90513" } }
```
4. Is the `public_graphql` schema not exposed properly?
Getting an 406 status or error message like:
```
{
"message": "The schema must be one of the following: public, storage"
}
```
Then be sure to expose the `graphql_public` in `Settings` > `Project settings` > `API`.
> The schema to expose in your API. Tables, views and stored procedures in this schema will get API endpoints.

================================================
FILE: app/.eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: app/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# typescript
*.tsbuildinfo
================================================
FILE: app/README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
================================================
FILE: app/gql/.gitignore
================================================
*.ts
*.tsx
================================================
FILE: app/lib/active-link.tsx
================================================
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
/**
* @source https://nextjs.org/docs/api-reference/next/link
* @source https://github.com/vercel/next.js/tree/canary/examples/active-class-name
*/
export function ActiveLink({
children,
activeClassName,
...props
}: {
children: React.ReactNode;
activeClassName: string;
href: string;
as?: string;
}) {
const { asPath, isReady } = useRouter();
const child = React.Children.only(children) as React.DetailedReactHTMLElement<
any,
HTMLElement
>;
const childClassName = child.props?.className || "";
const [className, setClassName] = React.useState(childClassName);
React.useEffect(() => {
// Check if the router fields are updated client-side
if (isReady) {
// Dynamic route will be matched via props.as
// Static route will be matched via props.href
const linkPathname = new URL(props.as || props.href, location.href)
.pathname;
// Using URL().pathname to get rid of query and hash
const activePathname = new URL(asPath, location.href).pathname;
const newClassName =
linkPathname === activePathname
? `${childClassName} ${activeClassName}`.trim()
: childClassName;
if (newClassName !== className) {
setClassName(newClassName);
}
}
}, [
asPath,
isReady,
props.as,
props.href,
childClassName,
activeClassName,
setClassName,
className,
]);
return (
<Link {...props}>
{React.cloneElement(child, {
className: className || null,
})}
</Link>
);
}
================================================
FILE: app/lib/comment-item.tsx
================================================
import React from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useMutation } from "urql";
import { Auth } from "@supabase/ui";
import { TrashIcon } from "@heroicons/react/outline";
import { CalendarIcon, UserIcon } from "./icons";
import { DocumentType, gql } from "../gql";
import { timeAgo } from "./time-ago";
const CommentItem_CommentFragment = gql(/* GraphQL */ `
fragment CommentItem_CommentFragment on Comment {
id
message
createdAt
post {
id
title
}
profile {
id
username
avatarUrl
}
}
`);
const CommentItem_DeleteCommentFragment = gql(/* GraphQL */ `
mutation CommentItem_DeleteComment($commentId: BigInt!) {
deleteFromCommentCollection(atMost: 1, filter: { id: { eq: $commentId } }) {
affectedCount
}
}
`);
export function CommentItem(props: {
comment: DocumentType<typeof CommentItem_CommentFragment>;
}) {
const router = useRouter();
const { user } = Auth.useUser();
const [deleteCommentMutation, deleteComment] = useMutation(
CommentItem_DeleteCommentFragment
);
const createdAt = React.useMemo(
() => timeAgo.format(new Date(props.comment.createdAt)),
[props.comment.createdAt]
);
React.useEffect(() => {
if (deleteCommentMutation.data) {
router.reload();
}
}, [deleteCommentMutation.data]);
return (
<div className="flex space-x-3 py-4">
<img
className="h-6 w-6 rounded-full"
src={props.comment.profile?.avatarUrl ?? undefined}
alt=""
/>
<div className="flex-1 space-y-1">
<div className="flex items-center justify-between">
<h3 className="text-sm font-medium">
<Link href={`/profile/${props.comment.profile?.id}`}>
<a className="text-gray-800 hover:text-green-500">
{props.comment.profile?.username}
</a>
</Link>
</h3>
<p className="text-sm text-gray-500">
<Link href={`/item/${props.comment.post?.id}`}>
<a className="text-gray-800 hover:text-green-500 inline-flex items-center text-sm">
<CalendarIcon className="w-4 h-4 mr-1" />
{createdAt}
</a>
</Link>
</p>
</div>
<p className="text-sm text-gray-500">
{props.comment.message}
{user?.id && user.id === props.comment.profile?.id ? (
<button
type="button"
className="inline-flex items-center ml-2 p-1 border border-transparent rounded-full shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
onClick={() => {
deleteComment({
commentId: props.comment.id,
});
}}
>
<TrashIcon className="h-2 w-2" />
</button>
) : null}
</p>
</div>
</div>
);
}
================================================
FILE: app/lib/container.tsx
================================================
import React from "react";
export function Container(props: { children: React.ReactNode }) {
return <main className="px-0 py-2">{props.children}</main>;
}
================================================
FILE: app/lib/feed-item.tsx
================================================
import { Auth } from "@supabase/ui";
import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
import { useMutation } from "urql";
import { Modal } from "@supabase/ui";
import { DocumentType, gql } from "../gql";
import {
CalendarIcon,
ChevronDownIcon,
ChevronUpIcon,
CommentIcon,
PointIcon,
TrashIcon,
UserIcon,
} from "./icons";
import { timeAgo } from "./time-ago";
const VoteButtons_PostFragment = gql(/* GraphQL */ `
fragment VoteButtons_PostFragment on Post {
id
upVoteByViewer: voteCollection(
filter: { profileId: { eq: $profileId }, direction: { eq: "UP" } }
) {
totalCount
}
downVoteByViewer: voteCollection(
filter: { profileId: { eq: $profileId }, direction: { eq: "DOWN" } }
) {
totalCount
}
}
`);
const VoteButtons_DeleteVoteMutation = gql(/* GraphQL */ `
mutation VoteButtons_DeleteVoteMutation($postId: BigInt!, $profileId: UUID!) {
deleteFromVoteCollection(
filter: { postId: { eq: $postId }, profileId: { eq: $profileId } }
) {
__typename
}
}
`);
const VoteButtons_VoteMutation = gql(/* GraphQL */ `
mutation VoteButtons_VoteMutation(
$postId: BigInt!
$profileId: UUID!
$voteDirection: String!
) {
insertIntoVoteCollection(
objects: [
{ postId: $postId, profileId: $profileId, direction: $voteDirection }
]
) {
__typename
affectedCount
records {
id
direction
}
}
}
`);
function VoteButtons(props: {
post: DocumentType<typeof VoteButtons_PostFragment>;
}) {
const router = useRouter();
const { user } = Auth.useUser();
const [, deleteVote] = useMutation(VoteButtons_DeleteVoteMutation);
const [voteMutation, vote] = useMutation(VoteButtons_VoteMutation);
React.useEffect(() => {
if (voteMutation.data) {
router.reload();
}
}, [voteMutation.data]);
return (
<div className="flex flex-col self-center mr-3 pb-8">
<button
onClick={async () => {
if (!user) {
router.push("/login");
} else if (props.post.upVoteByViewer?.totalCount === 0) {
await deleteVote({
postId: props.post.id,
profileId: user.id,
});
vote({
postId: props.post.id,
profileId: user.id,
voteDirection: "UP",
});
}
}}
>
<ChevronUpIcon
strokeWidth={props.post.upVoteByViewer?.totalCount !== 0 ? "4" : "2"}
/>
</button>
<button
onClick={async () => {
if (!user) {
router.push("/login");
} else if (props.post.downVoteByViewer?.totalCount === 0) {
await deleteVote({
postId: props.post.id,
profileId: user.id,
});
vote({
postId: props.post.id,
profileId: user.id,
voteDirection: "DOWN",
});
}
}}
>
<ChevronDownIcon
strokeWidth={
props.post.downVoteByViewer?.totalCount !== 0 ? "4" : "2"
}
/>
</button>
</div>
);
}
const FeedItem_PostFragment = gql(/* GraphQL */ `
fragment FeedItem_PostFragment on Post {
id
title
url
voteTotal
createdAt
commentCollection {
totalCount
}
profile {
id
username
avatarUrl
}
...VoteButtons_PostFragment
...DeleteButton_PostFragment
}
`);
export function FeedItem(props: {
post: DocumentType<typeof FeedItem_PostFragment>;
}) {
const { user } = Auth.useUser();
const createdAt = React.useMemo(
() => timeAgo.format(new Date(props.post.createdAt)),
[props.post.createdAt]
);
return (
<div className="py-1 flex flex-wrap md:flex-nowrap mb-8 border-gray-100 border-b-2">
<VoteButtons post={props.post} />
<div className="flex-1 md:flex-grow">
<Link href={props.post.url}>
<a>
<h2 className="text-2xl font-medium text-gray-900 hover:text-green-500 title-font mb-2">
{props.post.title}
</h2>
</a>
</Link>
<div className="flex items-center flex-wrap pb-4 border-gray-100 mt-auto w-full">
<span className="text-gray-400 mr-3 inline-flex items-center text-sm pr-3 py-1 border-r-2 border-gray-200">
<PointIcon className="w-4 h-4 mr-1" />
{props.post.voteTotal}{" "}
{props.post.voteTotal === 1 ? "point" : "points"}
</span>
<Link href={`/item/${props.post.id}`}>
<a className="text-gray-400 hover:text-green-400 mr-3 inline-flex items-center text-sm pr-3 py-1 border-r-2 border-gray-200">
<CommentIcon className="w-4 h-4 mr-1" />
{props.post.commentCollection?.totalCount}{" "}
{props.post.commentCollection?.totalCount === 1
? "comment"
: "comments"}
</a>
</Link>
<Link href={`/profile/${props.post.profile?.id}`}>
<a className="text-gray-400 hover:text-green-400 mr-3 inline-flex items-center text-sm pr-3 py-1 border-r-2 border-gray-200">
<img
className="inline-block h-4 w-4 rounded-full w-4 h-4 mr-1"
src={props.post.profile?.avatarUrl ?? ""}
/>
{props.post.profile?.username}
</a>
</Link>
<Link href={`/item/${props.post.id}`}>
<a className="text-gray-400 hover:text-green-400 inline-flex items-center text-sm">
<CalendarIcon className="w-4 h-4 mr-1" />
{createdAt}
</a>
</Link>
{user?.id && props.post.profile?.id === user?.id ? (
<DeleteButton post={props.post} />
) : null}
</div>
</div>
</div>
);
}
const DeleteButton_DeletePostMutation = gql(/* GraphQL */ `
mutation DeleteButton_DeletePostMutation($postId: BigInt!) {
deleteFromPostCollection(atMost: 1, filter: { id: { eq: $postId } }) {
affectedCount
}
}
`);
const DeleteButton_PostFragment = gql(/* GraphQL */ `
fragment DeleteButton_PostFragment on Post {
id
}
`);
const DeleteButton = (props: {
post: DocumentType<typeof DeleteButton_PostFragment>;
}) => {
const router = useRouter();
const [show, setShow] = React.useState(false);
const [deletePostMutation, deletePost] = useMutation(
DeleteButton_DeletePostMutation
);
React.useEffect(() => {
if (deletePostMutation.data) {
router.push("/");
}
}, [deletePostMutation.data, deletePostMutation.error]);
return (
<>
<button
className="text-gray-400 hover:text-green-400 inline-flex items-center text-sm pl-3 ml-3 border-l-2 border-gray-200"
onClick={() => setShow(true)}
>
<TrashIcon className="w-4 h-4 mr-1" />
Delete
</button>
<Modal
title="Do you want to delete your post?"
visible={show}
onCancel={() => setShow(false)}
onConfirm={() => {
deletePost({
postId: props.post.id,
});
}}
>
Deleting your post can not be reverted
</Modal>
</>
);
};
================================================
FILE: app/lib/footer.tsx
================================================
import Image from "next/image";
import Link from "next/link";
export function Footer() {
const navigation = {
main: [{ name: "🤔 How did we build this app?", href: "/about" }],
social: [
{
name: "Twitter",
href: "https://twitter.com/supabase",
icon: (props: any) => (
<svg fill="currentColor" viewBox="0 0 24 24" {...props}>
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
</svg>
),
},
{
name: "GitHub",
href: "https://github.com/supabase-community/supabase-graphql-example",
icon: (props: any) => (
<svg fill="currentColor" viewBox="0 0 24 24" {...props}>
<path
fillRule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clipRule="evenodd"
/>
</svg>
),
},
],
};
return (
<footer className="bg-white">
<div className="max-w-7xl mx-auto py-4 px-4 overflow-hidden sm:px-6 lg:px-8">
<div className="bg-white">
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 gap-8 md:grid-cols-2 lg:grid-cols-2">
<div className="col-span-1 flex justify-center md:col-span-2 lg:col-span-1">
<a href="https://www.supabase.com">
<img
className="h-12"
src="/supabase-logo-wordmark--light.svg"
alt="Supabase"
/>
</a>
</div>
<div className="col-span-1 flex justify-center md:col-span-2 lg:col-span-1">
<a href="https://www.the-guild.dev">
<img
className="h-12"
src="/the-guild-full-dark.svg"
alt="The Guild"
/>
</a>
</div>
</div>
</div>
</div>
<nav
className="-mx-5 -my-2 flex flex-wrap justify-center"
aria-label="Footer"
>
{navigation.main.map((item) => (
<div key={item.name} className="px-5 py-2">
<Link href={item.href}>
<a className="text-base text-gray-500 hover:text-gray-900">
{item.name}
</a>
</Link>
</div>
))}
</nav>
<div className="mt-8 flex justify-center space-x-6">
{navigation.social.map((item) => (
<a
key={item.name}
href={item.href}
className="text-gray-400 hover:text-gray-500"
>
<span className="sr-only">{item.name}</span>
<item.icon className="h-6 w-6" aria-hidden="true" />
</a>
))}
</div>
</div>
</footer>
);
}
================================================
FILE: app/lib/icons.tsx
================================================
import React from "react";
export function CalendarIcon(props: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<rect x={3} y={4} width={18} height={18} rx={2} ry={2} />
<path d="M16 2v4M8 2v4M3 10h18" />
</svg>
);
}
export function TrashIcon(props: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
</svg>
);
}
export function CommentIcon(props: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z"></path>
</svg>
);
}
export function PointIcon(props: { className?: string }) {
return (
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none" {...props}>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13 10V3L4 14h7v7l9-11h-7z"
></path>
</svg>
);
}
export function UserIcon(props: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
className="w-4 h-4 mr-1"
stroke="currentColor"
strokeWidth="2"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
<circle cx={12} cy={7} r={4} />
</svg>
);
}
export function SupabaseIcon(props: { className?: string; height: number }) {
return (
<svg fill="none" viewBox="0 0 109 113" {...props}>
<path
d="M63.708 110.284c-2.86 3.601-8.658 1.628-8.727-2.97l-1.007-67.251h45.22c8.19 0 12.758 9.46 7.665 15.874l-43.151 54.347Z"
fill="url(#a)"
/>
<path
d="M63.708 110.284c-2.86 3.601-8.658 1.628-8.727-2.97l-1.007-67.251h45.22c8.19 0 12.758 9.46 7.665 15.874l-43.151 54.347Z"
fill="url(#b)"
fillOpacity={0.2}
/>
<path
d="M45.317 2.071c2.86-3.601 8.657-1.628 8.726 2.97l.442 67.251H9.83c-8.19 0-12.759-9.46-7.665-15.875L45.317 2.072Z"
fill="#3ECF8E"
/>
<defs>
<linearGradient
id="a"
x1={53.974}
y1={54.974}
x2={94.163}
y2={71.829}
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#249361" />
<stop offset={1} stopColor="#3ECF8E" />
</linearGradient>
<linearGradient
id="b"
x1={36.156}
y1={30.578}
x2={54.484}
y2={65.081}
gradientUnits="userSpaceOnUse"
>
<stop />
<stop offset={1} stopOpacity={0} />
</linearGradient>
</defs>
</svg>
);
}
export function ChevronUpIcon(props: {
className?: string;
strokeWidth?: string;
}) {
return (
<svg
viewBox="0 0 24 24"
className="w-4 h-4 mr-1"
stroke="currentColor"
strokeWidth="2"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="m18 15-6-6-6 6" />
</svg>
);
}
export function ChevronDownIcon(props: {
className?: string;
strokeWidth?: string;
}) {
return (
<svg
viewBox="0 0 24 24"
className="w-4 h-4 mr-1"
stroke="currentColor"
strokeWidth="2"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="m6 9 6 6 6-6" />
</svg>
);
}
================================================
FILE: app/lib/loading.tsx
================================================
import React from "react";
import { LightningBoltIcon } from "@heroicons/react/solid";
export function Loading() {
return (
<div className="grid place-items-center h-80">
<LightningBoltIcon className="animate-bounce w-12 h-12 text-green-400" />
</div>
);
}
================================================
FILE: app/lib/main-section.tsx
================================================
import React from "react";
export function MainSection(props: { children: React.ReactNode }) {
return (
<main className="min-h-screen px-4 py-0 flex-1 flex flex-column max-w-screen-md mx-auto">
{props.children}
</main>
);
}
================================================
FILE: app/lib/navigation.tsx
================================================
import { Auth } from "@supabase/ui";
import Link from "next/link";
import React from "react";
import { ActiveLink } from "./active-link";
import { SupabaseIcon } from "./icons";
export function Navigation() {
const user = Auth.useUser();
return (
<header className="text-gray-600 body-font max-w-screen-md mx-auto">
<div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
<Link href="/">
<a className="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">
<SupabaseIcon height={24} />
<span className="ml-3 text-xl">supanews</span>
</a>
</Link>
<nav className="md:mr-auto md:ml-4 md:py-1 md:pl-4 md:border-l md:border-gray-400 flex flex-wrap items-center text-base justify-center">
<ActiveLink href="/" activeClassName="text-black">
<a className="mr-5 hover:text-gray-900 text-gray-400">feed</a>
</ActiveLink>
<ActiveLink href="/newest" activeClassName="text-black">
<a className="mr-5 hover:text-gray-900 text-gray-400">new</a>
</ActiveLink>
<ActiveLink href="/comments" activeClassName="text-black">
<a className="mr-5 hover:text-gray-900 text-gray-400">comments</a>
</ActiveLink>
<ActiveLink href="/submit" activeClassName="text-black">
<a className="mr-5 hover:text-gray-900 text-gray-400">submit</a>
</ActiveLink>
<ActiveLink href="/about" activeClassName="text-black">
<a className="mr-5 hover:text-gray-900 text-gray-400">about</a>
</ActiveLink>
</nav>
{user.user === null ? (
<Link href="/login">
<a className="inline-flex items-center mt-4 md:mt-0 md:mr-5 text-gray-400 hover:text-gray-900">
login
</a>
</Link>
) : (
<>
<ActiveLink href="/account" activeClassName="text-black">
<a className="inline-flex items-center mt-4 md:mt-0 md:mr-5 text-gray-400 hover:text-gray-900">
account
</a>
</ActiveLink>
<Link href="/logout">
<a className="inline-flex items-center mt-4 md:mt-0 text-gray-400 hover:text-gray-900">
logout
</a>
</Link>
</>
)}
</div>
</header>
);
}
================================================
FILE: app/lib/noop-uuid.ts
================================================
/**
* Noop UUID for GraphQL operations that require an UUID
*/
export const noopUUID = "00000000-0000-0000-0000-000000000000";
================================================
FILE: app/lib/supabase.tsx
================================================
import React from "react";
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { Auth } from "@supabase/ui";
const SupabaseClientContext = React.createContext<SupabaseClient | null>(null);
export function SupabaseProvider(props: { children: React.ReactNode }) {
const [client] = React.useState(() =>
createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL ?? "http://127.0.0.1:6969",
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "noop"
)
);
return (
<SupabaseClientContext.Provider value={client}>
<Auth.UserContextProvider supabaseClient={client}>
{props.children}
</Auth.UserContextProvider>
</SupabaseClientContext.Provider>
);
}
export function useSupabaseClient(): SupabaseClient {
const client = React.useContext(SupabaseClientContext);
if (client === null) {
throw new Error(
"Supabase client not provided via context.\n" +
"Did you forget to wrap your component tree with SupabaseProvider?"
);
}
return client;
}
================================================
FILE: app/lib/time-ago.ts
================================================
import TimeAgo from "javascript-time-ago";
import en from "javascript-time-ago/locale/en.json";
TimeAgo.addDefaultLocale(en);
export const timeAgo = new TimeAgo("en-US");
================================================
FILE: app/lib/urql.tsx
================================================
import React from "react";
import { createClient, Provider } from "urql";
import { useSupabaseClient } from "./supabase";
export function UrqlProvider(props: { children: React.ReactNode }) {
const supabaseClient = useSupabaseClient();
function getHeaders(): Record<string, string> {
const headers: Record<string, string> = {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
};
const authorization = supabaseClient.auth.session()?.access_token;
if (authorization) {
headers["authorization"] = `Bearer ${authorization}`;
}
return headers;
}
const [client] = React.useState(function createUrqlClient() {
return createClient({
url: `${process.env.NEXT_PUBLIC_SUPABASE_URL!}/graphql/v1`,
fetchOptions: function createFetchOptions() {
return { headers: getHeaders() };
},
});
});
return <Provider value={client}>{props.children}</Provider>;
}
================================================
FILE: app/lib/use-paginated-query.ts
================================================
import React from "react";
import { useQuery, UseQueryArgs, UseQueryResponse } from "urql";
/**
* Urql only supports "merge/infinite" pagination by adoptinh the GraphCache (a global normalized cache),
* which certainly is an overkill for this demo.
*
* This hook wraps `useQuery` from urql and adds a light-weight merge previous and current result API.
*/
export function usePaginatedQuery<Data = any, Variables = object>(
args: UseQueryArgs<Variables, Data> & {
/**
* Merge the old result with the new result.
*/
mergeResult: (oldData: Data, newData: Data) => Data;
}
): UseQueryResponse<Data, Variables> {
const [query, queryFn] = useQuery(args);
const { data, ...rest } = query;
const mergeRef = React.useRef({ current: data, last: data });
if (
data &&
mergeRef.current.current &&
query.data !== mergeRef.current.last
) {
mergeRef.current.current = args.mergeResult(mergeRef.current.current, data);
}
if (data != null && mergeRef.current.current == null) {
mergeRef.current.current = data;
}
mergeRef.current.last = query.data;
return [
{
...rest,
data: mergeRef.current.current,
},
queryFn,
];
}
================================================
FILE: app/next-env.d.ts
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
================================================
FILE: app/next.config.js
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = nextConfig;
================================================
FILE: app/package.json
================================================
{
"name": "app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@graphql-yoga/render-graphiql": "2.13.12",
"@heroicons/react": "1.0.6",
"@supabase/supabase-js": "1.35.7",
"@supabase/ui": "0.36.5",
"autoprefixer": "10.4.12",
"graphql": "16.6.0",
"javascript-time-ago": "2.5.7",
"next": "12.3.1",
"postcss": "8.4.17",
"react": "17.0.2",
"react-dom": "17.0.2",
"tailwindcss": "3.1.8",
"urql": "2.2.3"
},
"devDependencies": {
"@types/javascript-time-ago": "2.0.3",
"@types/node": "17.0.45",
"@types/react": "17.0.50",
"eslint": "8.24.0",
"eslint-config-next": "12.3.1",
"typescript": "4.6.2"
}
}
================================================
FILE: app/pages/_app.tsx
================================================
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { UrqlProvider } from "../lib/urql";
import { SupabaseProvider } from "../lib/supabase";
import { Navigation } from "../lib/navigation";
import { Footer } from "../lib/footer";
function MyApp({ Component, pageProps }: AppProps) {
return (
<SupabaseProvider>
<UrqlProvider>
<Navigation />
<Component {...pageProps} />
<Footer />
</UrqlProvider>
</SupabaseProvider>
);
}
export default MyApp;
================================================
FILE: app/pages/about.tsx
================================================
import React from "react";
import type { NextPage } from "next";
import Head from "next/head";
import Link from "next/link";
import {
CodeIcon,
CogIcon,
CollectionIcon,
ColorSwatchIcon,
DatabaseIcon,
GlobeAltIcon,
LightningBoltIcon,
ShieldCheckIcon,
TemplateIcon,
} from "@heroicons/react/outline";
import { Container } from "../lib/container";
const About: NextPage = () => {
const features = [
{
name: "PG GraphQL",
description: "Adds GraphQL support to your PostgreSQL database.",
icon: DatabaseIcon,
href: "https://supabase.github.io/pg_graphql/",
},
{
name: "GraphQL Code Generator",
description:
"Generate code from your GraphQL schema and operations with a simple CLI.",
icon: CodeIcon,
href: "https://www.graphql-code-generator.com",
},
{
name: "GraphQL Config",
description:
"One configuration for development environment with your GraphQL Schema.",
icon: CogIcon,
href: "https://www.graphql-config.com",
},
{
name: "Supabase",
description:
"Supabase has all the backend services you need to build a product with GraphQL.",
icon: LightningBoltIcon,
href: "https://www.supabase.com",
},
{
name: "Supabase Auth",
description:
"Supabase Auth provides user management with row level security.",
icon: ShieldCheckIcon,
href: "https://supabase.com/auth",
},
{
name: "Supabase UI",
description:
"An open-source UI component library inspired by Tailwind and AntDesign.",
icon: CollectionIcon,
href: "https://ui.supabase.io",
},
{
name: "GraphiQL",
description:
"GraphiQL is an in-browser IDE for writing, validating, and testing GraphQL queries.",
icon: TemplateIcon,
href: "https://www.graphql-yoga.com/docs/features/graphiql",
},
{
name: "urql",
description:
"urql is a highly customizable and versatile GraphQL client.",
icon: GlobeAltIcon,
href: "https://formidable.com/open-source/urql/",
},
{
name: "Tailwind CSS",
description:
"A utility-first CSS framework to rapidly build modern websites.",
icon: ColorSwatchIcon,
href: "https://tailwindcss.com",
},
];
const faqs = [
{
id: 1,
question: "GraphQL Query and Mutation Operations",
answer:
"The data on this page is fetched from the GraphQL layer auto-generated via pg_graphql.",
},
{
id: 2,
question: "Pagination",
answer:
"pg_graphql generates standardized pagination types and fields as defined by the GraphQL Cursor Connections Specification.",
},
{
id: 3,
question: "GraphQL Code Generation",
answer:
"GraphQL Code Generator introspects your GraphQL schema and operations and generates the types for full backend to frontend type-safety.",
},
{
id: 4,
question: "GraphQL Fragments",
answer:
"Components use GraphQL fragments to share logic between multiple queries and mutations.",
},
{
id: 5,
question: "pg_graphql Postgres Extension",
answer:
"pg_graphql generates a GraphQL API based on the Postgres schema.",
},
{
id: 6,
question: "Total Counts",
answer:
"Use pg_graphql's totalCount comment-based GraphQL Directive to count of the rows that match the query's filters.",
},
{
id: 7,
question: "Schema Inflection",
answer:
"Use pg_graphql's inflect_names comment-based GraphQL Directive to convert from snake_case to PascalCase for type names, and snake_case to camelCase for field names to match Javascript conventions when generating your GraphQL schema.",
},
{
id: 8,
question: "Supabase Postgres",
answer:
"Every Supabase project is a dedicated PostgreSQL database, trusted by millions of developers. It provide some of the most common extensions with a one-click install.",
},
{
id: 9,
question: "Supabase Auth",
answer:
"Authorization cannot get any easier. Every Supabase project comes with a complete User Management system that works without any additional tools.",
},
{
id: 10,
question: "Postgres RLS",
answer:
"Row level security on the Postgres layer ensures that viewers can only access what they are allowed to access and can only create records when authenticated.",
},
{
id: 11,
question: "Postgres Functions",
answer:
"Built-in support for SQL functions. These functions live inside your database, and they can be used with the Supabase API or invoked by a Postgres Trigger.",
},
{
id: 12,
question: "Postgres Triggers",
answer:
"Execute any SQL code after inserting, updating, or deleting data. We recalculate the feed scores by invoking a function each time someone votes (or change their vote).",
},
];
return (
<Container>
<Head>
<title>supanews | About</title>
<meta
name="description"
content="Everything you need to develop a GraphQL app."
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="relative bg-white py-4 sm:py-8 lg:py-12">
<div className="mx-auto max-w-md px-4 text-center sm:max-w-3xl sm:px-6 lg:px-8 lg:max-w-7xl">
<h2 className="text-base font-semibold tracking-wider text-green-600 uppercase">
GraphQL + Postgres + Tooling
</h2>
<p className="mt-2 text-3xl font-extrabold text-gray-900 tracking-tight sm:text-4xl">
Everything you need to develop a GraphQL app
</p>
<p className="mt-5 max-w-prose mx-auto text-xl text-gray-500">
Build your next GraphQL powered application like this one ... faster
and easier with open source tools from{" "}
<a className="text-gray-800" href="https://www.supabase.com">
Supabase
</a>
,{" "}
<a className="text-gray-800" href="https://www.the-guild.dev">
The Guild
</a>{" "}
and more.
</p>
<div className="mt-12">
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => (
<div key={feature.name} className="pt-6">
<div className="flow-root bg-gray-50 rounded-lg px-6 pb-8">
<Link href={feature.href}>
<a>
<div className="-mt-6">
<div>
<span className="inline-flex items-center justify-center p-3 bg-gradient-to-r from-teal-500 to-green-600 rounded-md shadow-lg">
<feature.icon
className="h-6 w-6 text-white"
aria-hidden="true"
/>
</span>
</div>
<h3 className="mt-8 text-lg font-medium text-gray-900 tracking-tight">
{feature.name}
</h3>
<p className="mt-5 text-base text-gray-500">
{feature.description}
</p>
</div>
</a>
</Link>
</div>
</div>
))}
</div>
</div>
</div>
<div className="bg-white">
<div className="max-w-7xl mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:px-8">
<h2 className="text-3xl font-extrabold text-gray-900 text-center">
Technologies Used
</h2>
<div className="mt-12">
<dl className="space-y-10 md:space-y-0 md:grid md:grid-cols-2 md:gap-x-8 md:gap-y-12 lg:grid-cols-3">
{faqs.map((faq) => (
<div key={faq.id}>
<dt className="text-lg leading-6 font-medium text-gray-900">
{faq.question}
</dt>
<dd className="mt-2 text-base text-gray-500">
{faq.answer}
</dd>
</div>
))}
</dl>
</div>
</div>
</div>
</div>
</Container>
);
};
export default About;
================================================
FILE: app/pages/account.tsx
================================================
import React from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import { Auth, Button } from "@supabase/ui";
import { CombinedError, useMutation, useQuery } from "urql";
import { Input } from "@supabase/ui";
import { DocumentType, gql } from "../gql";
import { Container } from "../lib/container";
import { Loading } from "../lib/loading";
import { MainSection } from "../lib/main-section";
const UserProfileQuery = gql(/* GraphQL */ `
query UserProfileQuery($profileId: UUID!) {
profileCollection(filter: { id: { eq: $profileId } }) {
edges {
node {
...AccountProfileFragment
}
}
}
}
`);
const Account: NextPage = () => {
const session = Auth.useUser();
const [profileQuery] = useQuery({
query: UserProfileQuery,
variables: { profileId: session.user?.id },
pause: session.user === null,
});
const router = useRouter();
const profile = profileQuery.data?.profileCollection?.edges?.[0].node;
React.useEffect(() => {
if (session.user === null || (profileQuery.data && profile === null)) {
router.replace("/login");
}
}, [session.user, profile, profileQuery.data]);
if (profile) {
return <AccountForm profile={profile} />;
}
return (
<div className="w-full">{profileQuery.fetching ? <Loading /> : null}</div>
);
return null;
};
const ProfileFragment = gql(/* GraphQL */ `
fragment AccountProfileFragment on Profile {
id
username
website
bio
}
`);
const UpdateProfileMutation = gql(/* GraphQL */ `
mutation updateProfile(
$userId: UUID!
$newUsername: String!
$newWebsite: String!
$newBio: String!
) {
updateProfileCollection(
filter: { id: { eq: $userId } }
set: { username: $newUsername, website: $newWebsite, bio: $newBio }
) {
affectedCount
records {
id
username
website
}
}
}
`);
function extractExpectedGraphQLErrors(
error: CombinedError | undefined
): null | string {
if (error === undefined) {
return null;
}
for (const graphQLError of error.graphQLErrors) {
if (graphQLError.message.includes("usernamelength")) {
return "Username must have a minimum length of 3 characters.";
}
if (graphQLError.message.includes("Profile_username_key")) {
return "The name is already taken.";
}
}
return null;
}
function AccountForm(props: { profile: DocumentType<typeof ProfileFragment> }) {
const [username, setUsername] = React.useState(props.profile.username ?? "");
const [website, setWebsite] = React.useState(props.profile.website ?? "");
const [bio, setBio] = React.useState(props.profile.bio ?? "");
const [updateProfileMutation, updateProfile] = useMutation(
UpdateProfileMutation
);
const errorState = extractExpectedGraphQLErrors(updateProfileMutation.error);
return (
<Container>
<MainSection>
<section className="container px-5 py-24 mx-auto max-w-md">
<h1 className="font-semibold text-xl tracking-tight mb-5">Account</h1>
<div className="mb-4">
<label htmlFor="username" className="block mb-2">
Name
</label>
<Input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="mb-4">
<label htmlFor="website" className="block mb-2">
Website
</label>
<Input
id="website"
type="website"
value={website}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div className="mb-4">
<label htmlFor="bio" className="block mb-2">
Bio
</label>
<textarea
id="bio"
className="w-full border-solid border-2 border-gray-100 rounded-sm"
value={bio}
onChange={(e) => setBio(e.target.value)}
/>
</div>
<div>
<Button
onClick={() =>
updateProfile({
userId: props.profile.id,
newUsername: username,
newWebsite: website,
newBio: bio,
})
}
disabled={updateProfileMutation.fetching}
>
{updateProfileMutation.fetching ? "Loading ..." : "Update"}
</Button>
</div>
<div>{errorState}</div>
</section>
</MainSection>
</Container>
);
}
export default Account;
================================================
FILE: app/pages/api/graphiql.ts
================================================
import { renderGraphiQL } from "@graphql-yoga/render-graphiql";
import { NextApiRequest, NextApiResponse } from "next";
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
export default async (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).end(
renderGraphiQL({
endpoint: process.env.NEXT_PUBLIC_SUPABASE_URL + "/graphql/v1",
headers: JSON.stringify({
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
}),
credentials: "omit",
})
);
};
================================================
FILE: app/pages/comments.tsx
================================================
import { Button } from "@supabase/ui";
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import React from "react";
import { useQuery } from "urql";
import { gql } from "../gql";
import { CommentItem } from "../lib/comment-item";
import { Container } from "../lib/container";
import { Loading } from "../lib/loading";
import { MainSection } from "../lib/main-section";
import { usePaginatedQuery } from "../lib/use-paginated-query";
const CommentsRouteQuery = gql(/* GraphQL */ `
query CommentsRouteQuery($after: Cursor) {
comments: commentCollection(
orderBy: [{ createdAt: DescNullsFirst }]
first: 15
after: $after
) {
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
id
...CommentItem_CommentFragment
}
}
}
}
`);
const Comments: NextPage = () => {
const [lastCursor, setLastCursor] = React.useState<string | undefined>(
undefined
);
const [commentsQuery] = usePaginatedQuery({
query: CommentsRouteQuery,
variables: {
after: lastCursor,
},
mergeResult(oldData, newData) {
return {
...oldData,
...newData,
comments: {
...oldData.comments!,
...newData.comments!,
edges: [...oldData.comments!.edges, ...newData.comments!.edges],
},
};
},
});
return (
<Container>
<MainSection>
<Head>
<title>supanews | Comments</title>
<meta name="description" content="New comments" />
<link rel="icon" href="/favicon.ico" />
</Head>
<section className="text-gray-600 body-font overflow-hidden w-full">
<div className="container px-3 py-24 mx-auto">
<div className="-my-8 divide-y-2 divide-gray-100">
{commentsQuery?.data?.comments?.edges.map((edge) => (
<CommentItem comment={edge.node!} key={edge.cursor} />
))}
</div>
{commentsQuery.fetching ? <Loading /> : null}
</div>
{commentsQuery.data?.comments?.pageInfo.hasNextPage ? (
<div className="flex justify-center content-center">
<Button
onClick={() =>
setLastCursor(
commentsQuery.data?.comments?.pageInfo.endCursor ??
undefined
)
}
>
Load more.
</Button>
</div>
) : null}
</section>
</MainSection>
</Container>
);
};
export default Comments;
================================================
FILE: app/pages/index.tsx
================================================
import { Auth, Button } from "@supabase/ui";
import type { NextPage } from "next";
import Head from "next/head";
import React from "react";
import { gql } from "../gql";
import { Container } from "../lib/container";
import { FeedItem } from "../lib/feed-item";
import { Loading } from "../lib/loading";
import { MainSection } from "../lib/main-section";
import { noopUUID } from "../lib/noop-uuid";
import { usePaginatedQuery } from "../lib/use-paginated-query";
const IndexRouteQuery = gql(/* GraphQL */ `
query IndexRouteQuery($profileId: UUID!, $after: Cursor) {
feed: postCollection(
orderBy: [{ voteRank: AscNullsFirst }]
first: 15
after: $after
) {
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
id
...FeedItem_PostFragment
}
}
}
}
`);
const Home: NextPage = () => {
const { user } = Auth.useUser();
const [lastCursor, setLastCursor] = React.useState<string | undefined>(
undefined
);
const [indexQuery] = usePaginatedQuery({
query: IndexRouteQuery,
variables: {
profileId: user?.id ?? noopUUID,
after: lastCursor,
},
mergeResult(oldData, newData) {
return {
...oldData,
...newData,
feed: {
...oldData.feed!,
...newData.feed!,
edges: [...oldData.feed!.edges, ...newData.feed!.edges],
},
};
},
});
return (
<Container>
<Head>
<title>supanews | Feed</title>
<meta name="description" content="What is hot?" />
<link rel="icon" href="/favicon.ico" />
</Head>
<MainSection>
<section className="text-gray-600 body-font overflow-hidden w-full">
<div className="container px-3 py-24 mx-auto">
<div className="-my-8">
{indexQuery?.data?.feed?.edges.map((edge) => (
<FeedItem post={edge.node!} key={edge.cursor} />
))}
{indexQuery.fetching ? <Loading /> : null}
</div>
</div>
{indexQuery.data?.feed?.pageInfo.hasNextPage ? (
<div className="flex justify-center content-center">
<Button
onClick={() => {
setLastCursor(
indexQuery.data?.feed?.pageInfo.endCursor ?? undefined
);
}}
>
Load more.
</Button>
</div>
) : null}
</section>
</MainSection>
</Container>
);
};
export default Home;
================================================
FILE: app/pages/item/[postId].tsx
================================================
import { Auth, Button } from "@supabase/ui";
import React from "react";
import type { NextPage } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import { useMutation, useQuery } from "urql";
import { gql } from "../../gql";
import { CommentItem } from "../../lib/comment-item";
import { Container } from "../../lib/container";
import { FeedItem } from "../../lib/feed-item";
import { Loading } from "../../lib/loading";
import { MainSection } from "../../lib/main-section";
import { noopUUID } from "../../lib/noop-uuid";
const ItemRouteQuery = gql(/* GraphQL */ `
query ItemRouteQuery($postId: BigInt!, $profileId: UUID!) {
post: postCollection(filter: { id: { eq: $postId } }, first: 1) {
edges {
cursor
node {
id
...FeedItem_PostFragment
comments: commentCollection(
first: 15
orderBy: [{ createdAt: DescNullsLast }]
) {
edges {
cursor
node {
id
...CommentItem_CommentFragment
}
}
pageInfo {
hasNextPage
}
}
}
}
}
}
`);
const PostCommentMutation = gql(/* GraphQL */ `
mutation postComment($profileId: UUID!, $message: String!, $postId: BigInt) {
insertIntoCommentCollection(
objects: [{ profileId: $profileId, message: $message, postId: $postId }]
) {
affectedCount
}
}
`);
function PostCommentForm(props: { postId: string }) {
const [postCommentMutation, postComment] = useMutation(PostCommentMutation);
const [message, setMessage] = React.useState("");
const { user } = Auth.useUser();
const router = useRouter();
React.useEffect(() => {
if (postCommentMutation.data) {
router.reload();
}
}, [postCommentMutation.data]);
return (
<form>
<div className="mb-2 font-bold">Write comment</div>
<textarea
className="w-full border-solid border-2 border-gray-100 rounded-sm"
value={message}
onChange={(ev) => setMessage(ev.target.value)}
/>
<Button
onClick={() => {
postComment({
profileId: user?.id!,
message,
postId: props.postId,
});
}}
>
add comment
</Button>
</form>
);
}
const Item: NextPage = () => {
const { user } = Auth.useUser();
const router = useRouter();
const { postId } = router.query;
const [itemRouteQuery] = useQuery({
query: ItemRouteQuery,
variables: {
postId,
profileId: user?.id ?? noopUUID,
},
});
const post = itemRouteQuery?.data?.post?.edges?.[0];
return (
<Container>
<MainSection>
<div className="h-screen w-full">
{itemRouteQuery.fetching ? <Loading /> : null}
{post?.node == null ? null : (
<>
<Head>
<title>supanews | {post.node?.title}</title>
<meta name="description" content={post.node?.url} />
</Head>
<section className="text-gray-600 body-font overflow-hidden w-full">
<div className="container px-5 py-24 mx-auto">
<FeedItem post={post.node} key={post.cursor} />
<div className="max-w-md">
{user && <PostCommentForm postId={post.node.id} />}
<div className="mt-10">
{post.node?.comments?.edges.map((edge) => (
<CommentItem comment={edge.node!} key={edge.cursor} />
))}
</div>
</div>
</div>
</section>
</>
)}
</div>
</MainSection>
</Container>
);
};
export default Item;
================================================
FILE: app/pages/login.tsx
================================================
import React from "react";
import type { NextPage } from "next";
import Head from "next/head";
import { Auth } from "@supabase/ui";
import { useSupabaseClient } from "../lib/supabase";
import { useRouter } from "next/router";
import { Container } from "../lib/container";
import { MainSection } from "../lib/main-section";
const LogIn: NextPage = () => {
const { user } = Auth.useUser();
const supabaseClient = useSupabaseClient();
const router = useRouter();
React.useEffect(() => {
if (user !== null) {
router.replace("/account");
}
}, []);
if (user) {
return null;
}
return (
<Container>
<Head>
<title>supanews | Login</title>
<meta name="description" content="Sign in to submit posts and vote." />
<link rel="icon" href="/favicon.ico" />
</Head>
<MainSection>
<div className="m-width-md mx-auto">
<h1 className="font-semibold text-xl tracking-tight mb-5">Login</h1>
<Auth supabaseClient={supabaseClient} providers={["github"]} />
</div>
</MainSection>
</Container>
);
};
export default LogIn;
================================================
FILE: app/pages/logout.tsx
================================================
import React from "react";
import type { NextPage } from "next";
import { Auth } from "@supabase/ui";
import { useSupabaseClient } from "../lib/supabase";
import { useRouter } from "next/router";
const LogOut: NextPage = () => {
const supabaseClient = useSupabaseClient();
const router = useRouter();
React.useEffect(() => {
supabaseClient.auth.signOut().then(() => router.replace("/"));
}, []);
return null;
};
export default LogOut;
================================================
FILE: app/pages/newest.tsx
================================================
import { Auth, Button } from "@supabase/ui";
import type { NextPage } from "next";
import Head from "next/head";
import React from "react";
import { gql } from "../gql";
import { Container } from "../lib/container";
import { FeedItem } from "../lib/feed-item";
import { Loading } from "../lib/loading";
import { MainSection } from "../lib/main-section";
import { noopUUID } from "../lib/noop-uuid";
import { usePaginatedQuery } from "../lib/use-paginated-query";
const NewestRouteQuery = gql(/* GraphQL */ `
query NewestRouteQuery($profileId: UUID!, $after: Cursor) {
feed: postCollection(
orderBy: [{ createdAt: DescNullsFirst }]
first: 15
after: $after
) {
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
id
...FeedItem_PostFragment
}
}
}
}
`);
const Newest: NextPage = () => {
const { user } = Auth.useUser();
const [lastCursor, setLastCursor] = React.useState<string | undefined>(
undefined
);
const [newestQuery] = usePaginatedQuery({
query: NewestRouteQuery,
variables: {
profileId: user?.id ?? noopUUID,
after: lastCursor,
},
mergeResult(oldData, newData) {
return {
...oldData,
...newData,
feed: {
...oldData.feed!,
...newData.feed!,
edges: [...oldData.feed!.edges, ...newData.feed!.edges],
},
};
},
});
return (
<Container>
<Head>
<title>supanews | Newest</title>
<meta name="description" content="What is hot?" />
<link rel="icon" href="/favicon.ico" />
</Head>
<MainSection>
<section className="text-gray-600 body-font overflow-hidden w-full">
<div className="container px-5 py-24 mx-auto">
<div className="-my-8">
{newestQuery?.data?.feed?.edges.map((edge) => (
<FeedItem post={edge.node!} key={edge.cursor} />
))}
</div>
{newestQuery.fetching ? <Loading /> : null}
</div>
{newestQuery.data?.feed?.pageInfo.hasNextPage ? (
<div className="flex justify-center content-center">
<Button
onClick={() => {
setLastCursor(
newestQuery.data?.feed?.pageInfo.endCursor ?? undefined
);
}}
>
Load more.
</Button>
</div>
) : null}
</section>
</MainSection>
</Container>
);
};
export default Newest;
================================================
FILE: app/pages/profile/[profileId].tsx
================================================
import type { NextPage } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import { useQuery } from "urql";
import { gql } from "../../gql";
import { CommentItem } from "../../lib/comment-item";
import { Container } from "../../lib/container";
import { FeedItem } from "../../lib/feed-item";
import { Loading } from "../../lib/loading";
import { MainSection } from "../../lib/main-section";
const ProfileRouteQuery = gql(/* GraphQL */ `
query ProfileRouteQuery($profileId: UUID!) {
profileCollection(filter: { id: { eq: $profileId } }) {
edges {
node {
id
username
bio
avatarUrl
website
latestPosts: postCollection(
orderBy: [{ createdAt: DescNullsFirst }]
first: 15
) {
pageInfo {
hasNextPage
}
edges {
cursor
node {
id
...FeedItem_PostFragment
}
}
}
latestComments: commentCollection(
orderBy: [{ createdAt: DescNullsFirst }]
first: 15
) {
pageInfo {
hasNextPage
}
edges {
cursor
node {
id
...CommentItem_CommentFragment
}
}
}
}
}
}
}
`);
const Profile: NextPage = () => {
const router = useRouter();
const { profileId } = router.query;
const [profileQuery] = useQuery({
query: ProfileRouteQuery,
variables: {
profileId,
},
});
const profile = profileQuery.data?.profileCollection?.edges?.[0].node;
return (
<Container>
<Head>
<title>supanews | {profile?.username}</title>
<meta name="description" content="What is hot?" />
<link rel="icon" href="/favicon.ico" />
</Head>
<MainSection>
<div className="w-full">
{profileQuery.fetching ? <Loading /> : null}
{profile == null ? null : (
<section className="text-gray-600 body-font overflow-hidden">
<div className="container px-5 py-24 pt-10 mx-auto">
<h1 className="font-semibold text-xl tracking-tight mb-5">
Profile
</h1>{" "}
<div>
<span className="inline-block font-bold pr-2 w-20">User</span>{" "}
{profile.username}
</div>
<div>
<span className="inline-block font-bold pr-2 w-20">
Avatar
</span>{" "}
<img
className="inline-block h-6 w-6 rounded-full"
src={profile.avatarUrl ?? ""}
/>
</div>
<div>
<span className="inline-block font-bold pr-2 w-20">
Website
</span>{" "}
{profile.website}
</div>
<div className="mb-10">
<span className="inline-block font-bold pr-2 w-20">Bio</span>{" "}
{profile.bio}
</div>
<h1 className="font-semibold text-xl tracking-tight mb-5">
Latest Posts
</h1>
<div>
{profile.latestPosts?.edges.map((edge) => (
<FeedItem post={edge.node!} key={edge.cursor} />
))}
</div>
<h1 className="font-semibold text-xl tracking-tight mb-5">
Latest Comments
</h1>
{profile.latestComments?.edges.map((edge) => (
<CommentItem comment={edge.node!} key={edge.cursor} />
))}
</div>
</section>
)}
</div>
</MainSection>
</Container>
);
};
export default Profile;
================================================
FILE: app/pages/submit.tsx
================================================
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import { Auth, Input, Button } from "@supabase/ui";
import { gql } from "../gql";
import { CombinedError, useMutation } from "urql";
import { Container } from "../lib/container";
import { MainSection } from "../lib/main-section";
const CreatePostMutation = gql(/* GraphQL */ `
mutation createPostMutation($input: PostInsertInput!) {
insertIntoPostCollection(objects: [$input]) {
affectedCount
records {
id
}
}
}
`);
function extractExpectedGraphQLErrors(
error: CombinedError | undefined
): null | string {
if (error === undefined) {
return null;
}
for (const graphQLError of error.graphQLErrors) {
if (graphQLError.message.includes("Post_url_key")) {
return "This news has already been submitted.";
}
}
return null;
}
const Submit: NextPage = () => {
const session = Auth.useUser();
const router = useRouter();
const [createPostMutation, createPost] = useMutation(CreatePostMutation);
const [title, setTitle] = React.useState("");
const [url, setUrl] = React.useState("");
React.useEffect(() => {
if (session.user === null) {
router.replace("/login");
}
}, []);
React.useEffect(() => {
if (createPostMutation.fetching === false && createPostMutation.data) {
router.push(
`/item/${createPostMutation.data.insertIntoPostCollection?.records[0].id}`
);
}
}, [createPostMutation.fetching]);
if (session.user == null) {
return null;
}
const error = extractExpectedGraphQLErrors(createPostMutation.error);
return (
<Container>
<Head>
<title>supanews | Submit New Item</title>
<meta name="description" content="What is hot?" />
<link rel="icon" href="/favicon.ico" />
</Head>
<MainSection>
<form className="container px-5 py-24 mx-auto max-w-md">
<h1 className="font-semibold text-xl tracking-tight mb-5">Submit</h1>
<div className="mb-4">
<Input
placeholder="Title"
value={title}
onChange={(ev) => setTitle(ev.target.value)}
/>
</div>
<div className="mb-4">
<Input
placeholder="URL"
value={url}
onChange={(ev) => setUrl(ev.target.value)}
/>
</div>
<div className="mb-4 min-h-1">{error}</div>
{createPostMutation.fetching ? (
<Button type="outline">Loading...</Button>
) : (
<Button
onClick={() => {
createPost({
input: {
url,
title,
profileId: session.user!.id,
score: 1,
},
});
}}
>
Submit
</Button>
)}
</form>
</MainSection>
</Container>
);
};
export default Submit;
================================================
FILE: app/postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
================================================
FILE: app/styles/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
================================================
FILE: app/tailwind.config.js
================================================
module.exports = {
content: ["./pages/**/*.{js,ts,jsx,tsx}", "./lib/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
================================================
FILE: app/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
================================================
FILE: data/db/backup.sql
================================================
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: auth; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA auth;
--
-- Name: extensions; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA extensions;
--
-- Name: pg_graphql; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pg_graphql WITH SCHEMA public;
--
-- Name: EXTENSION pg_graphql; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pg_graphql IS 'GraphQL support';
--
-- Name: pg_net; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pg_net WITH SCHEMA extensions;
--
-- Name: EXTENSION pg_net; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pg_net IS 'Async HTTP';
--
-- Name: pgbouncer; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA pgbouncer;
--
-- Name: realtime; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA realtime;
--
-- Name: storage; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA storage;
--
-- Name: supabase_functions; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA supabase_functions;
--
-- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA extensions;
--
-- Name: EXTENSION pg_stat_statements; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pg_stat_statements IS 'track planning and execution statistics of all SQL statements executed';
--
-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA extensions;
--
-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
--
-- Name: pgjwt; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pgjwt WITH SCHEMA extensions;
--
-- Name: EXTENSION pgjwt; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pgjwt IS 'JSON Web Token API for Postgresql';
--
-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA extensions;
--
-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)';
--
-- Name: direction; Type: TYPE; Schema: public; Owner: -
--
CREATE TYPE public.direction AS ENUM (
'UP',
'DOWN'
);
--
-- Name: action; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.action AS ENUM (
'INSERT',
'UPDATE',
'DELETE',
'TRUNCATE',
'ERROR'
);
--
-- Name: equality_op; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.equality_op AS ENUM (
'eq',
'neq',
'lt',
'lte',
'gt',
'gte'
);
--
-- Name: user_defined_filter; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.user_defined_filter AS (
column_name text,
op realtime.equality_op,
value text
);
--
-- Name: wal_column; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.wal_column AS (
name text,
type text,
value jsonb,
is_pkey boolean,
is_selectable boolean
);
--
-- Name: wal_rls; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.wal_rls AS (
wal jsonb,
is_rls_enabled boolean,
subscription_ids uuid[],
errors text[]
);
--
-- Name: email(); Type: FUNCTION; Schema: auth; Owner: -
--
CREATE FUNCTION auth.email() RETURNS text
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.email', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'email')
)::text
$$;
--
-- Name: role(); Type: FUNCTION; Schema: auth; Owner: -
--
CREATE FUNCTION auth.role() RETURNS text
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.role', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role')
)::text
$$;
--
-- Name: uid(); Type: FUNCTION; Schema: auth; Owner: -
--
CREATE FUNCTION auth.uid() RETURNS uuid
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.sub', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
)::uuid
$$;
--
-- Name: grant_pg_cron_access(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.grant_pg_cron_access() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
schema_is_cron bool;
BEGIN
schema_is_cron = (
SELECT n.nspname = 'cron'
FROM pg_event_trigger_ddl_commands() AS ev
LEFT JOIN pg_catalog.pg_namespace AS n
ON ev.objid = n.oid
);
IF schema_is_cron
THEN
grant usage on schema cron to postgres with grant option;
alter default privileges in schema cron grant all on tables to postgres with grant option;
alter default privileges in schema cron grant all on functions to postgres with grant option;
alter default privileges in schema cron grant all on sequences to postgres with grant option;
alter default privileges for user supabase_admin in schema cron grant all
on sequences to postgres with grant option;
alter default privileges for user supabase_admin in schema cron grant all
on tables to postgres with grant option;
alter default privileges for user supabase_admin in schema cron grant all
on functions to postgres with grant option;
grant all privileges on all tables in schema cron to postgres with grant option;
END IF;
END;
$$;
--
-- Name: FUNCTION grant_pg_cron_access(); Type: COMMENT; Schema: extensions; Owner: -
--
COMMENT ON FUNCTION extensions.grant_pg_cron_access() IS 'Grants access to pg_cron';
--
-- Name: grant_pg_net_access(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.grant_pg_net_access() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_event_trigger_ddl_commands() AS ev
JOIN pg_extension AS ext
ON ev.objid = ext.oid
WHERE ext.extname = 'pg_net'
)
THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_collect_response(request_id bigint, async boolean) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_collect_response(request_id bigint, async boolean) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_collect_response(request_id bigint, async boolean) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_collect_response(request_id bigint, async boolean) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END;
$$;
--
-- Name: FUNCTION grant_pg_net_access(); Type: COMMENT; Schema: extensions; Owner: -
--
COMMENT ON FUNCTION extensions.grant_pg_net_access() IS 'Grants access to pg_net';
--
-- Name: notify_api_restart(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.notify_api_restart() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
BEGIN
NOTIFY pgrst, 'reload schema';
END;
$$;
--
-- Name: FUNCTION notify_api_restart(); Type: COMMENT; Schema: extensions; Owner: -
--
COMMENT ON FUNCTION extensions.notify_api_restart() IS 'Sends a notification to the API to restart. If your database schema has changed, this is required so that Supabase can rebuild the relationships.';
--
-- Name: pgrst_ddl_watch(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.pgrst_ddl_watch() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
cmd record;
BEGIN
FOR cmd IN SELECT * FROM pg_event_trigger_ddl_commands()
LOOP
IF cmd.command_tag IN (
'CREATE SCHEMA', 'ALTER SCHEMA'
, 'CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO', 'ALTER TABLE'
, 'CREATE FOREIGN TABLE', 'ALTER FOREIGN TABLE'
, 'CREATE VIEW', 'ALTER VIEW'
, 'CREATE MATERIALIZED VIEW', 'ALTER MATERIALIZED VIEW'
, 'CREATE FUNCTION', 'ALTER FUNCTION'
, 'CREATE TRIGGER'
, 'CREATE TYPE', 'ALTER TYPE'
, 'CREATE RULE'
, 'COMMENT'
)
-- don't notify in case of CREATE TEMP table or other objects created on pg_temp
AND cmd.schema_name is distinct from 'pg_temp'
THEN
NOTIFY pgrst, 'reload schema';
END IF;
END LOOP;
END; $$;
--
-- Name: pgrst_drop_watch(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.pgrst_drop_watch() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
LOOP
IF obj.object_type IN (
'schema'
, 'table'
, 'foreign table'
, 'view'
, 'materialized view'
, 'function'
, 'trigger'
, 'type'
, 'rule'
)
AND obj.is_temporary IS false -- no pg_temp objects
THEN
NOTIFY pgrst, 'reload schema';
END IF;
END LOOP;
END; $$;
--
-- Name: get_auth(text); Type: FUNCTION; Schema: pgbouncer; Owner: -
--
CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(username text, password text)
LANGUAGE plpgsql SECURITY DEFINER
AS $$
BEGIN
RAISE WARNING 'PgBouncer auth request: %', p_usename;
RETURN QUERY
SELECT usename::TEXT, passwd::TEXT FROM pg_catalog.pg_shadow
WHERE usename = p_usename;
END;
$$;
--
-- Name: graphql(text, text, jsonb, jsonb); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.graphql("operationName" text DEFAULT NULL::text, query text DEFAULT NULL::text, variables jsonb DEFAULT NULL::jsonb, extensions jsonb DEFAULT NULL::jsonb) RETURNS jsonb
LANGUAGE sql
AS $$
select graphql.resolve(query, coalesce(variables, '{}'));
$$;
--
-- Name: handle_new_user(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.handle_new_user() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
AS $$
begin
insert into public."Profile" (id, "avatarUrl", username)
values (new.id, 'https://www.gravatar.com/avatar/' || md5(new.email) || '?d=mp', split_part(new.email, '@', 1) || '-' || floor(random() * 10000));
return new;
end;
$$;
--
-- Name: update_vote_counts(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.update_vote_counts() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
AS $$
BEGIN
WITH r AS (
SELECT
coalesce("Vote"."postId", "Post".id) AS "postId",
count(1) "voteTotal",
count(1) FILTER (WHERE direction = 'UP') "upVoteTotal",
count(1) FILTER (WHERE direction = 'DOWN') "downVoteTotal",
coalesce(sum(
CASE WHEN direction = 'UP' THEN
1
WHEN direction = 'DOWN' THEN
- 1
ELSE
0
END), 0) "voteDelta",
sum(
CASE WHEN direction = 'UP' THEN
1
WHEN direction = 'DOWN' THEN
- 1
ELSE
0
END) - 1 / (DATE_PART('hour', now() - max("Vote"."createdAt")) + 2) ^ 1.8 AS "score",
rank() OVER (ORDER BY coalesce(sum( CASE WHEN direction = 'UP' THEN
1
WHEN direction = 'DOWN' THEN
- 1
ELSE
0
END) - 1 / (DATE_PART('hour', now() - max("Vote"."createdAt")) + 2) ^ 1.8, '-infinity')
DESC,
"Post"."createdAt" DESC,
"Post".title ASC) "voteRank"
FROM
"Vote"
RIGHT JOIN "Post" ON "Vote"."postId" = "Post".id
GROUP BY
"Post".id,
"Vote"."postId"
)
UPDATE
public. "Post"
SET
"upVoteTotal" = r. "upVoteTotal",
"downVoteTotal" = r. "downVoteTotal",
"voteTotal" = r. "voteTotal",
"voteDelta" = r. "voteDelta",
"voteRank" = r. "voteRank",
"score" = r. "score"
FROM
r
WHERE
r."postId" = public. "Post".id;
RETURN new;
END;
$$;
--
-- Name: apply_rls(jsonb, integer); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.apply_rls(wal jsonb, max_record_bytes integer DEFAULT (1024 * 1024)) RETURNS SETOF realtime.wal_rls
LANGUAGE plpgsql
AS $$
declare
-- Regclass of the table e.g. public.notes
entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;
-- I, U, D, T: insert, update ...
action realtime.action = (
case wal ->> 'action'
when 'I' then 'INSERT'
when 'U' then 'UPDATE'
when 'D' then 'DELETE'
else 'ERROR'
end
);
-- Is row level security enabled for the table
is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;
subscriptions realtime.subscription[] = array_agg(subs)
from
realtime.subscription subs
where
subs.entity = entity_;
-- Subscription vars
roles regrole[] = array_agg(distinct us.claims_role)
from
unnest(subscriptions) us;
working_role regrole;
claimed_role regrole;
claims jsonb;
subscription_id uuid;
subscription_has_access bool;
visible_to_subscription_ids uuid[] = '{}';
-- structured info for wal's columns
columns realtime.wal_column[];
-- previous identity values for update/delete
old_columns realtime.wal_column[];
error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;
-- Primary jsonb output for record
output jsonb;
begin
perform set_config('role', null, true);
columns =
array_agg(
(
x->>'name',
x->>'type',
realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),
(pks ->> 'name') is not null,
true
)::realtime.wal_column
)
from
jsonb_array_elements(wal -> 'columns') x
left join jsonb_array_elements(wal -> 'pk') pks
on (x ->> 'name') = (pks ->> 'name');
old_columns =
array_agg(
(
x->>'name',
x->>'type',
realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),
(pks ->> 'name') is not null,
true
)::realtime.wal_column
)
from
jsonb_array_elements(wal -> 'identity') x
left join jsonb_array_elements(wal -> 'pk') pks
on (x ->> 'name') = (pks ->> 'name');
for working_role in select * from unnest(roles) loop
-- Update `is_selectable` for columns and old_columns
columns =
array_agg(
(
c.name,
c.type,
c.value,
c.is_pkey,
pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')
)::realtime.wal_column
)
from
unnest(columns) c;
old_columns =
array_agg(
(
c.name,
c.type,
c.value,
c.is_pkey,
pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')
)::realtime.wal_column
)
from
unnest(old_columns) c;
if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then
return next (
null,
is_rls_enabled,
-- subscriptions is already filtered by entity
(select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),
array['Error 400: Bad Request, no primary key']
)::realtime.wal_rls;
-- The claims role does not have SELECT permission to the primary key of entity
elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then
return next (
null,
is_rls_enabled,
(select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),
array['Error 401: Unauthorized']
)::realtime.wal_rls;
else
output = jsonb_build_object(
'schema', wal ->> 'schema',
'table', wal ->> 'table',
'type', action,
'commit_timestamp', to_char(
(wal ->> 'timestamp')::timestamptz,
'YYYY-MM-DD"T"HH24:MI:SS"Z"'
),
'columns', (
select
jsonb_agg(
jsonb_build_object(
'name', pa.attname,
'type', pt.typname
)
order by pa.attnum asc
)
from
pg_attribute pa
join pg_type pt
on pa.atttypid = pt.oid
where
attrelid = entity_
and attnum > 0
and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')
)
)
-- Add "record" key for insert and update
|| case
when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)
when action in ('INSERT', 'UPDATE') then
jsonb_build_object(
'record',
(select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)
)
else '{}'::jsonb
end
-- Add "old_record" key for update and delete
|| case
when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)
when action in ('UPDATE', 'DELETE') then
jsonb_build_object(
'old_record',
(select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)
)
else '{}'::jsonb
end;
-- Create the prepared statement
if is_rls_enabled and action <> 'DELETE' then
if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then
deallocate walrus_rls_stmt;
end if;
execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);
end if;
visible_to_subscription_ids = '{}';
for subscription_id, claims in (
select
subs.subscription_id,
subs.claims
from
unnest(subscriptions) subs
where
subs.entity = entity_
and subs.claims_role = working_role
and realtime.is_visible_through_filters(columns, subs.filters)
) loop
if not is_rls_enabled or action = 'DELETE' then
visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;
else
-- Check if RLS allows the role to see the record
perform
set_config('role', working_role::text, true),
set_config('request.jwt.claims', claims::text, true);
execute 'execute walrus_rls_stmt' into subscription_has_access;
if subscription_has_access then
visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;
end if;
end if;
end loop;
perform set_config('role', null, true);
return next (
output,
is_rls_enabled,
visible_to_subscription_ids,
case
when error_record_exceeds_max_size then array['Error 413: Payload Too Large']
else '{}'
end
)::realtime.wal_rls;
end if;
end loop;
perform set_config('role', null, true);
end;
$$;
--
-- Name: build_prepared_statement_sql(text, regclass, realtime.wal_column[]); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.build_prepared_statement_sql(prepared_statement_name text, entity regclass, columns realtime.wal_column[]) RETURNS text
LANGUAGE sql
AS $$
/*
Builds a sql string that, if executed, creates a prepared statement to
tests retrive a row from *entity* by its primary key columns.
Example
select realtime.build_prepared_statment_sql('public.notes', '{"id"}'::text[], '{"bigint"}'::text[])
*/
select
'prepare ' || prepared_statement_name || ' as
select
exists(
select
1
from
' || entity || '
where
' || string_agg(quote_ident(pkc.name) || '=' || quote_nullable(pkc.value #>> '{}') , ' and ') || '
)'
from
unnest(columns) pkc
where
pkc.is_pkey
group by
entity
$$;
--
-- Name: cast(text, regtype); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime."cast"(val text, type_ regtype) RETURNS jsonb
LANGUAGE plpgsql IMMUTABLE
AS $$
declare
res jsonb;
begin
execute format('select to_jsonb(%L::'|| type_::text || ')', val) into res;
return res;
end
$$;
--
-- Name: check_equality_op(realtime.equality_op, regtype, text, text); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.check_equality_op(op realtime.equality_op, type_ regtype, val_1 text, val_2 text) RETURNS boolean
LANGUAGE plpgsql IMMUTABLE
AS $$
/*
Casts *val_1* and *val_2* as type *type_* and check the *op* condition for truthiness
*/
declare
op_symbol text = (
case
when op = 'eq' then '='
when op = 'neq' then '!='
when op = 'lt' then '<'
when op = 'lte' then '<='
when op = 'gt' then '>'
when op = 'gte' then '>='
else 'UNKNOWN OP'
end
);
res boolean;
begin
execute format('select %L::'|| type_::text || ' ' || op_symbol || ' %L::'|| type_::text, val_1, val_2) into res;
return res;
end;
$$;
--
-- Name: is_visible_through_filters(realtime.wal_column[], realtime.user_defined_filter[]); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.is_visible_through_filters(columns realtime.wal_column[], filters realtime.user_defined_filter[]) RETURNS boolean
LANGUAGE sql IMMUTABLE
AS $$
/*
Should the record be visible (true) or filtered out (false) after *filters* are applied
*/
select
-- Default to allowed when no filters present
coalesce(
sum(
realtime.check_equality_op(
op:=f.op,
type_:=col.type::regtype,
-- cast jsonb to text
val_1:=col.value #>> '{}',
val_2:=f.value
)::int
) = count(1),
true
)
from
unnest(filters) f
join unnest(columns) col
on f.column_name = col.name;
$$;
--
-- Name: quote_wal2json(regclass); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.quote_wal2json(entity regclass) RETURNS text
LANGUAGE sql IMMUTABLE STRICT
AS $$
select
(
select string_agg('' || ch,'')
from unnest(string_to_array(nsp.nspname::text, null)) with ordinality x(ch, idx)
where
not (x.idx = 1 and x.ch = '"')
and not (
x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)
and x.ch = '"'
)
)
|| '.'
|| (
select string_agg('' || ch,'')
from unnest(string_to_array(pc.relname::text, null)) with ordinality x(ch, idx)
where
not (x.idx = 1 and x.ch = '"')
and not (
x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)
and x.ch = '"'
)
)
from
pg_class pc
join pg_namespace nsp
on pc.relnamespace = nsp.oid
where
pc.oid = entity
$$;
--
-- Name: subscription_check_filters(); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.subscription_check_filters() RETURNS trigger
LANGUAGE plpgsql
AS $$
/*
Validates that the user defined filters for a subscription:
- refer to valid columns that the claimed role may access
- values are coercable to the correct column type
*/
declare
col_names text[] = coalesce(
array_agg(c.column_name order by c.ordinal_position),
'{}'::text[]
)
from
information_schema.columns c
where
format('%I.%I', c.table_schema, c.table_name)::regclass = new.entity
and pg_catalog.has_column_privilege(
(new.claims ->> 'role'),
format('%I.%I', c.table_schema, c.table_name)::regclass,
c.column_name,
'SELECT'
);
filter realtime.user_defined_filter;
col_type regtype;
begin
for filter in select * from unnest(new.filters) loop
-- Filtered column is valid
if not filter.column_name = any(col_names) then
raise exception 'invalid column for filter %', filter.column_name;
end if;
-- Type is sanitized and safe for string interpolation
col_type = (
select atttypid::regtype
from pg_catalog.pg_attribute
where attrelid = new.entity
and attname = filter.column_name
);
if col_type is null then
raise exception 'failed to lookup type for column %', filter.column_name;
end if;
-- raises an exception if value is not coercable to type
perform realtime.cast(filter.value, col_type);
end loop;
-- Apply consistent order to filters so the unique constraint on
-- (subscription_id, entity, filters) can't be tricked by a different filter order
new.filters = coalesce(
array_agg(f order by f.column_name, f.op, f.value),
'{}'
) from unnest(new.filters) f;
return new;
end;
$$;
--
-- Name: to_regrole(text); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.to_regrole(role_name text) RETURNS regrole
LANGUAGE sql IMMUTABLE
AS $$ select role_name::regrole $$;
--
-- Name: extension(text); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.extension(name text) RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
_filename text;
BEGIN
select string_to_array(name, '/') into _parts;
select _parts[array_length(_parts,1)] into _filename;
-- @todo return the last part instead of 2
return split_part(_filename, '.', 2);
END
$$;
--
-- Name: filename(text); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.filename(name text) RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[array_length(_parts,1)];
END
$$;
--
-- Name: foldername(text); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.foldername(name text) RETURNS text[]
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[1:array_length(_parts,1)-1];
END
$$;
--
-- Name: get_size_by_bucket(); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.get_size_by_bucket() RETURNS TABLE(size bigint, bucket_id text)
LANGUAGE plpgsql
AS $$
BEGIN
return query
select sum((metadata->>'size')::int) as size, obj.bucket_id
from "storage".objects as obj
group by obj.bucket_id;
END
$$;
--
-- Name: search(text, text, integer, integer, integer); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.search(prefix text, bucketname text, limits integer DEFAULT 100, levels integer DEFAULT 1, offsets integer DEFAULT 0) RETURNS TABLE(name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb)
LANGUAGE plpgsql
AS $$
BEGIN
return query
with files_folders as (
select path_tokens[levels] as folder
from storage.objects
where objects.name ilike prefix || '%'
and bucket_id = bucketname
GROUP by folder
limit limits
offset offsets
)
select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
left join storage.objects
on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname;
END
$$;
--
-- Name: http_request(); Type: FUNCTION; Schema: supabase_functions; Owner: -
--
CREATE FUNCTION supabase_functions.http_request() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
SET search_path TO 'supabase_functions'
AS $$
DECLARE
request_id bigint;
payload jsonb;
url text := TG_ARGV[0]::text;
method text := TG_ARGV[1]::text;
headers jsonb DEFAULT '{}'::jsonb;
params jsonb DEFAULT '{}'::jsonb;
timeout_ms integer DEFAULT 1000;
BEGIN
IF url IS NULL OR url = 'null' THEN
RAISE EXCEPTION 'url argument is missing';
END IF;
IF method IS NULL OR method = 'null' THEN
RAISE EXCEPTION 'method argument is missing';
END IF;
IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
headers = '{"Content-Type": "application/json"}'::jsonb;
ELSE
headers = TG_ARGV[2]::jsonb;
END IF;
IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
params = '{}'::jsonb;
ELSE
params = TG_ARGV[3]::jsonb;
END IF;
IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
timeout_ms = 1000;
ELSE
timeout_ms = TG_ARGV[4]::integer;
END IF;
CASE
WHEN method = 'GET' THEN
SELECT http_get INTO request_id FROM net.http_get(
url,
params,
headers,
timeout_ms
);
WHEN method = 'POST' THEN
payload = jsonb_build_object(
'old_record', OLD,
'record', NEW,
'type', TG_OP,
'table', TG_TABLE_NAME,
'schema', TG_TABLE_SCHEMA
);
SELECT http_post INTO request_id FROM net.http_post(
url,
payload,
params,
headers,
timeout_ms
);
ELSE
RAISE EXCEPTION 'method argument % is invalid', method;
END CASE;
INSERT INTO supabase_functions.hooks
(hook_table_id, hook_name, request_id)
VALUES
(TG_RELID, TG_NAME, request_id);
RETURN NEW;
END
$$;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: audit_log_entries; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.audit_log_entries (
instance_id uuid,
id uuid NOT NULL,
payload json,
created_at timestamp with time zone
);
--
-- Name: TABLE audit_log_entries; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.audit_log_entries IS 'Auth: Audit trail for user actions.';
--
-- Name: identities; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.identities (
id text NOT NULL,
user_id uuid NOT NULL,
identity_data jsonb NOT NULL,
provider text NOT NULL,
last_sign_in_at timestamp with time zone,
created_at timestamp with time zone,
updated_at timestamp with time zone
);
--
-- Name: TABLE identities; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.identities IS 'Auth: Stores identities associated to a user.';
--
-- Name: instances; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.instances (
id uuid NOT NULL,
uuid uuid,
raw_base_config text,
created_at timestamp with time zone,
updated_at timestamp with time zone
);
--
-- Name: TABLE instances; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.instances IS 'Auth: Manages users across multiple sites.';
--
-- Name: refresh_tokens; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.refresh_tokens (
instance_id uuid,
id bigint NOT NULL,
token character varying(255),
user_id character varying(255),
revoked boolean,
created_at timestamp with time zone,
updated_at timestamp with time zone,
parent character varying(255)
);
--
-- Name: TABLE refresh_tokens; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.refresh_tokens IS 'Auth: Store of tokens used to refresh JWT tokens once they expire.';
--
-- Name: refresh_tokens_id_seq; Type: SEQUENCE; Schema: auth; Owner: -
--
CREATE SEQUENCE auth.refresh_tokens_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: refresh_tokens_id_seq; Type: SEQUENCE OWNED BY; Schema: auth; Owner: -
--
ALTER SEQUENCE auth.refresh_tokens_id_seq OWNED BY auth.refresh_tokens.id;
--
-- Name: schema_migrations; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.schema_migrations (
version character varying(255) NOT NULL
);
--
-- Name: TABLE schema_migrations; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.schema_migrations IS 'Auth: Manages updates to the auth system.';
--
-- Name: users; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.users (
instance_id uuid,
id uuid NOT NULL,
aud character varying(255),
role character varying(255),
email character varying(255),
encrypted_password character varying(255),
email_confirmed_at timestamp with time zone,
invited_at timestamp with time zone,
confirmation_token character varying(255),
confirmation_sent_at timestamp with time zone,
recovery_token character varying(255),
recovery_sent_at timestamp with time zone,
email_change_token_new character varying(255),
email_change character varying(255),
email_change_sent_at timestamp with time zone,
last_sign_in_at timestamp with time zone,
raw_app_meta_data jsonb,
raw_user_meta_data jsonb,
is_super_admin boolean,
created_at timestamp with time zone,
updated_at timestamp with time zone,
phone character varying(15) DEFAULT NULL::character varying,
phone_confirmed_at timestamp with time zone,
phone_change character varying(15) DEFAULT ''::character varying,
phone_change_token character varying(255) DEFAULT ''::character varying,
phone_change_sent_at timestamp with time zone,
confirmed_at timestamp with time zone GENERATED ALWAYS AS (LEAST(email_confirmed_at, phone_confirmed_at)) STORED,
email_change_token_current character varying(255) DEFAULT ''::character varying,
email_change_confirm_status smallint DEFAULT 0,
banned_until timestamp with time zone,
CONSTRAINT users_email_change_confirm_status_check CHECK (((email_change_confirm_status >= 0) AND (email_change_confirm_status <= 2)))
);
--
-- Name: TABLE users; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.users IS 'Auth: Stores user login data within a secure schema.';
--
-- Name: Comment; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Comment" (
id bigint NOT NULL,
"createdAt" timestamp with time zone DEFAULT now(),
"updatedAt" timestamp with time zone,
message text NOT NULL,
"profileId" uuid NOT NULL,
"postId" bigint NOT NULL
);
--
-- Name: TABLE "Comment"; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public."Comment" IS '@graphql({"totalCount": {"enabled": true}})';
--
-- Name: Comment_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
ALTER TABLE public."Comment" ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public."Comment_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- Name: Vote; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Vote" (
id bigint NOT NULL,
"createdAt" timestamp with time zone DEFAULT now(),
"updatedAt" timestamp with time zone,
"profileId" uuid NOT NULL,
"postId" bigint NOT NULL,
direction public.direction NOT NULL
);
--
-- Name: TABLE "Vote"; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public."Vote" IS '@graphql({"totalCount": {"enabled": true}})';
--
-- Name: DownVote_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
ALTER TABLE public."Vote" ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public."DownVote_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- Name: Post; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Post" (
id bigint NOT NULL,
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
"updatedAt" timestamp with time zone,
title text NOT NULL,
url text NOT NULL,
"profileId" uuid NOT NULL,
"upVoteTotal" integer DEFAULT 0 NOT NULL,
"downVoteTotal" integer DEFAULT 0 NOT NULL,
"voteTotal" integer DEFAULT 0 NOT NULL,
"voteRank" integer DEFAULT 1 NOT NULL,
score real DEFAULT '0'::real,
"voteDelta" integer DEFAULT 0 NOT NULL,
CONSTRAINT post_title_length CHECK ((char_length(title) > 0)),
CONSTRAINT post_url_length CHECK ((char_length(url) > 0))
);
--
-- Name: TABLE "Post"; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public."Post" IS '@graphql({"totalCount": {"enabled": true}})';
--
-- Name: Post_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
ALTER TABLE public."Post" ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public."Post_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- Name: Profile; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Profile" (
id uuid NOT NULL,
updatedat timestamp with time zone,
username text,
"avatarUrl" text,
website text,
bio text,
CONSTRAINT usernamelength CHECK ((char_length(username) >= 3))
);
--
-- Name: TABLE "Profile"; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public."Profile" IS '@graphql({"totalCount": {"enabled": true}})';
--
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.schema_migrations (
version character varying(255) NOT NULL
);
--
-- Name: schema_migrations; Type: TABLE; Schema: realtime; Owner: -
--
CREATE TABLE realtime.schema_migrations (
version bigint NOT NULL,
inserted_at timestamp(0) without time zone
);
--
-- Name: subscription; Type: TABLE; Schema: realtime; Owner: -
--
CREATE TABLE realtime.subscription (
id bigint NOT NULL,
subscription_id uuid NOT NULL,
entity regclass NOT NULL,
filters realtime.user_defined_filter[] DEFAULT '{}'::realtime.user_defined_filter[] NOT NULL,
claims jsonb NOT NULL,
claims_role regrole GENERATED ALWAYS AS (realtime.to_regrole((claims ->> 'role'::text))) STORED NOT NULL,
created_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL
);
--
-- Name: subscription_id_seq; Type: SEQUENCE; Schema: realtime; Owner: -
--
ALTER TABLE realtime.subscription ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME realtime.subscription_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- Name: buckets; Type: TABLE; Schema: storage; Owner: -
--
CREATE TABLE storage.buckets (
id text NOT NULL,
name text NOT NULL,
owner uuid,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now(),
public boolean DEFAULT false
);
--
-- Name: migrations; Type: TABLE; Schema: storage; Owner: -
--
CREATE TABLE storage.migrations (
id integer NOT NULL,
name character varying(100) NOT NULL,
hash character varying(40) NOT NULL,
executed_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP
);
--
-- Name: objects; Type: TABLE; Schema: storage; Owner: -
--
CREATE TABLE storage.objects (
id uuid DEFAULT extensions.uuid_generate_v4() NOT NULL,
bucket_id text,
name text,
owner uuid,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now(),
last_accessed_at timestamp with time zone DEFAULT now(),
metadata jsonb
);
--
-- Name: hooks; Type: TABLE; Schema: supabase_functions; Owner: -
--
CREATE TABLE supabase_functions.hooks (
id bigint NOT NULL,
hook_table_id integer NOT NULL,
hook_name text NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
request_id bigint
);
--
-- Name: TABLE hooks; Type: COMMENT; Schema: supabase_functions; Owner: -
--
COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
--
-- Name: hooks_id_seq; Type: SEQUENCE; Schema: supabase_functions; Owner: -
--
CREATE SEQUENCE supabase_functions.hooks_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: hooks_id_seq; Type: SEQUENCE OWNED BY; Schema: supabase_functions; Owner: -
--
ALTER SEQUENCE supabase_functions.hooks_id_seq OWNED BY supabase_functions.hooks.id;
--
-- Name: migrations; Type: TABLE; Schema: supabase_functions; Owner: -
--
CREATE TABLE supabase_functions.migrations (
version text NOT NULL,
inserted_at timestamp with time zone DEFAULT now() NOT NULL
);
--
-- Name: refresh_tokens id; Type: DEFAULT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.refresh_tokens ALTER COLUMN id SET DEFAULT nextval('auth.refresh_tokens_id_seq'::regclass);
--
-- Name: hooks id; Type: DEFAULT; Schema: supabase_functions; Owner: -
--
ALTER TABLE ONLY supabase_functions.hooks ALTER COLUMN id SET DEFAULT nextval('supabase_functions.hooks_id_seq'::regclass);
--
-- Name: audit_log_entries audit_log_entries_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.audit_log_entries
ADD CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id);
--
-- Name: identities identities_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.identities
ADD CONSTRAINT identities_pkey PRIMARY KEY (provider, id);
--
-- Name: instances instances_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.instances
ADD CONSTRAINT instances_pkey PRIMARY KEY (id);
--
-- Name: refresh_tokens refresh_tokens_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.refresh_tokens
ADD CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id);
--
-- Name: refresh_tokens refresh_tokens_token_unique; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.refresh_tokens
ADD CONSTRAINT refresh_tokens_token_unique UNIQUE (token);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: users users_email_key; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.users
ADD CONSTRAINT users_email_key UNIQUE (email);
--
-- Name: users users_phone_key; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.users
ADD CONSTRAINT users_phone_key UNIQUE (phone);
--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
--
-- Name: Comment Comment_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Comment"
ADD CONSTRAINT "Comment_pkey" PRIMARY KEY (id);
--
-- Name: Vote DownVote_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Vote"
ADD CONSTRAINT "DownVote_pkey" PRIMARY KEY (id);
--
-- Name: Post Post_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Post"
ADD CONSTRAINT "Post_pkey" PRIMARY KEY (id);
--
-- Name: Post Post_url_key; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Post"
ADD CONSTRAINT "Post_url_key" UNIQUE (url);
--
-- Name: Profile Profile_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Profile"
ADD CONSTRAINT "Profile_pkey" PRIMARY KEY (id);
--
-- Name: Profile Profile_username_key; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Profile"
ADD CONSTRAINT "Profile_username_key" UNIQUE (username);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: subscription pk_subscription; Type: CONSTRAINT; Schema: realtime; Owner: -
--
ALTER TABLE ONLY realtime.subscription
ADD CONSTRAINT pk_subscription PRIMARY KEY (id);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--
ALTER TABLE ONLY realtime.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: buckets buckets_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.buckets
ADD CONSTRAINT buckets_pkey PRIMARY KEY (id);
--
-- Name: migrations migrations_name_key; Type: CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.migrations
ADD CONSTRAINT migrations_name_key UNIQUE (name);
--
-- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.migrations
ADD CONSTRAINT migrations_pkey PRIMARY KEY (id);
--
-- Name: objects objects_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.objects
ADD CONSTRAINT objects_pkey PRIMARY KEY (id);
--
-- Name: hooks hooks_pkey; Type: CONSTRAINT; Schema: supabase_functions; Owner: -
--
ALTER TABLE ONLY supabase_functions.hooks
ADD CONSTRAINT hooks_pkey PRIMARY KEY (id);
--
-- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: supabase_functions; Owner: -
--
ALTER TABLE ONLY supabase_functions.migrations
ADD CONSTRAINT migrations_pkey PRIMARY KEY (version);
--
-- Name: audit_logs_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id);
--
-- Name: identities_user_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX identities_user_id_idx ON auth.identities USING btree (user_id);
--
-- Name: refresh_tokens_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id);
--
-- Name: refresh_tokens_instance_id_user_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id);
--
-- Name: refresh_tokens_parent_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX refresh_tokens_parent_idx ON auth.refresh_tokens USING btree (parent);
--
-- Name: refresh_tokens_token_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree (token);
--
-- Name: users_instance_id_email_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, lower((email)::text));
--
-- Name: users_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id);
--
-- Name: idx_one_vote_per_post; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX idx_one_vote_per_post ON public."Vote" USING btree ("profileId", "postId");
--
-- Name: idx_unique_post_url; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX idx_unique_post_url ON public."Post" USING btree (url);
--
-- Name: ix_realtime_subscription_entity; Type: INDEX; Schema: realtime; Owner: -
--
CREATE INDEX ix_realtime_subscription_entity ON realtime.subscription USING hash (entity);
--
-- Name: subscription_subscription_id_entity_filters_key; Type: INDEX; Schema: realtime; Owner: -
--
CREATE UNIQUE INDEX subscription_subscription_id_entity_filters_key ON realtime.subscription USING btree (subscription_id, entity, filters);
--
-- Name: bname; Type: INDEX; Schema: storage; Owner: -
--
CREATE UNIQUE INDEX bname ON storage.buckets USING btree (name);
--
-- Name: bucketid_objname; Type: INDEX; Schema: storage; Owner: -
--
CREATE UNIQUE INDEX bucketid_objname ON storage.objects USING btree (bucket_id, name);
--
-- Name: name_prefix_search; Type: INDEX; Schema: storage; Owner: -
--
CREATE INDEX name_prefix_search ON storage.objects USING btree (name text_pattern_ops);
--
-- Name: supabase_functions_hooks_h_table_id_h_name_idx; Type: INDEX; Schema: supabase_functions; Owner: -
--
CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
--
-- Name: supabase_functions_hooks_request_id_idx; Type: INDEX; Schema: supabase_functions; Owner: -
--
CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
--
-- Name: users on_auth_user_created; Type: TRIGGER; Schema: auth; Owner: -
--
CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
--
-- Name: Vote on_vote_created; Type: TRIGGER; Schema: public; Owner: -
--
CREATE TRIGGER on_vote_created AFTER INSERT ON public."Vote" FOR EACH ROW EXECUTE FUNCTION public.update_vote_counts();
--
-- Name: Vote on_vote_deleted; Type: TRIGGER; Schema: public; Owner: -
--
CREATE TRIGGER on_vote_deleted AFTER DELETE ON public."Vote" FOR EACH ROW EXECUTE FUNCTION public.update_vote_counts();
--
-- Name: subscription tr_check_filters; Type: TRIGGER; Schema: realtime; Owner: -
--
CREATE TRIGGER tr_check_filters BEFORE INSERT OR UPDATE ON realtime.subscription FOR EACH ROW EXECUTE FUNCTION realtime.subscription_check_filters();
--
-- Name: identities identities_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.identities
ADD CONSTRAINT identities_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
--
-- Name: refresh_tokens refresh_tokens_parent_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.refresh_tokens
ADD CONSTRAINT refresh_tokens_parent_fkey FOREIGN KEY (parent) REFERENCES auth.refresh_tokens(token);
--
-- Name: Comment Comment_postId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Comment"
ADD CONSTRAINT "Comment_postId_fkey" FOREIGN KEY ("postId") REFERENCES public."Post"(id) ON DELETE CASCADE;
--
-- Name: Comment Comment_profileId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Comment"
ADD CONSTRAINT "Comment_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES public."Profile"(id);
--
-- Name: Vote DownVote_profileId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Vote"
ADD CONSTRAINT "DownVote_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES public."Profile"(id);
--
-- Name: Post Post_profileId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Post"
ADD CONSTRAINT "Post_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES public."Profile"(id);
--
-- Name: Profile Profile_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Profile"
ADD CONSTRAINT "Profile_id_fkey" FOREIGN KEY (id) REFERENCES auth.users(id);
--
-- Name: Vote Vote_postId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Vote"
ADD CONSTRAINT "Vote_postId_fkey" FOREIGN KEY ("postId") REFERENCES public."Post"(id) ON DELETE CASCADE;
--
-- Name: buckets buckets_owner_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.buckets
ADD CONSTRAINT buckets_owner_fkey FOREIGN KEY (owner) REFERENCES auth.users(id);
--
-- Name: objects objects_bucketId_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.objects
ADD CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY (bucket_id) REFERENCES storage.buckets(id);
--
-- Name: objects objects_owner_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.objects
ADD CONSTRAINT objects_owner_fkey FOREIGN KEY (owner) REFERENCES auth.users(id);
--
-- Name: hooks hooks_request_id_fkey; Type: FK CONSTRAINT; Schema: supabase_functions; Owner: -
--
ALTER TABLE ONLY supabase_functions.hooks
ADD CONSTRAINT hooks_request_id_fkey FOREIGN KEY (request_id) REFERENCES net.http_request_queue(id) ON DELETE CASCADE;
--
-- Name: Post All users can view posts; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "All users can view posts" ON public."Post" FOR SELECT USING (true);
--
-- Name: Comment; Type: ROW SECURITY; Schema: public; Owner: -
--
ALTER TABLE public."Comment" ENABLE ROW LEVEL SECURITY;
--
-- Name: Comment Everyone can view comments; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Everyone can view comments" ON public."Comment" FOR SELECT USING (true);
--
-- Name: Vote Everyone can view votes; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Everyone can view votes" ON public."Vote" FOR SELECT USING (true);
--
-- Name: Comment Only authenticated users can comment; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Only authenticated users can comment" ON public."Comment" FOR INSERT WITH CHECK ((auth.role() = 'authenticated'::text));
--
-- Name: Post Only authenticated users can create posts; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Only authenticated users can create posts" ON public."Post" FOR INSERT WITH CHECK ((auth.role() = 'authenticated'::text));
--
-- Name: Vote Only authenticated users can vote; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Only authenticated users can vote" ON public."Vote" FOR INSERT WITH CHECK ((auth.role() = 'authenticated'::text));
--
-- Name: Post; Type: ROW SECURITY; Schema: public; Owner: -
--
ALTER TABLE public."Post" ENABLE ROW LEVEL SECURITY;
--
-- Name: Profile; Type: ROW SECURITY; Schema: public; Owner: -
--
ALTER TABLE public."Profile" ENABLE ROW LEVEL SECURITY;
--
-- Name: Profile Public profiles are viewable by everyone.; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Public profiles are viewable by everyone." ON public."Profile" FOR SELECT USING (true);
--
-- Name: Comment User can edit their own comments; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "User can edit their own comments" ON public."Comment" FOR UPDATE USING ((auth.uid() = "profileId")) WITH CHECK ((auth.uid() = "profileId"));
--
-- Name: Vote Users can change their vote; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Users can change their vote" ON public."Vote" FOR UPDATE USING ((auth.uid() = "profileId")) WITH CHECK ((auth.uid() = "profileId"));
--
-- Name: Comment Users can delete their own comments; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Users can delete their own comments" ON public."Comment" FOR DELETE USING ((auth.uid() = "profileId"));
--
-- Name: Post Users can delete their own posts; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Users can delete their own posts" ON public."Post" FOR DELETE USING ((auth.uid() = "profileId"));
--
-- Name: Vote Users can delete their own votes; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Users can delete their own votes" ON public."Vote" FOR DELETE USING ((auth.uid() = "profileId"));
--
-- Name: Post Users can edit their own posts; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Users can edit their own posts" ON public."Post" FOR UPDATE USING ((auth.uid() = "profileId")) WITH CHECK ((auth.uid() = "profileId"));
--
-- Name: Profile Users can insert their own profile.; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Users can insert their own profile." ON public."Profile" FOR INSERT WITH CHECK ((auth.uid() = id));
--
-- Name: Profile Users can update own profile.; Type: POLICY; Schema: public; Owner: -
--
CREATE POLICY "Users can update own profile." ON public."Profile" FOR UPDATE USING ((auth.uid() = id));
--
-- Name: Vote; Type: ROW SECURITY; Schema: public; Owner: -
--
ALTER TABLE public."Vote" ENABLE ROW LEVEL SECURITY;
--
-- Name: schema_migrations; Type: ROW SECURITY; Schema: public; Owner: -
--
ALTER TABLE public.schema_migrations ENABLE ROW LEVEL SECURITY;
--
-- Name: objects Anyone can update an avatar.; Type: POLICY; Schema: storage; Owner: -
--
CREATE POLICY "Anyone can update an avatar." ON storage.objects FOR UPDATE WITH CHECK ((bucket_id = 'avatars'::text));
--
-- Name: objects Anyone can upload an avatar.; Type: POLICY; Schema: storage; Owner: -
--
CREATE POLICY "Anyone can upload an avatar." ON storage.objects FOR INSERT WITH CHECK ((bucket_id = 'avatars'::text));
--
-- Name: objects Avatar images are publicly accessible.; Type: POLICY; Schema: storage; Owner: -
--
CREATE POLICY "Avatar images are publicly accessible." ON storage.objects FOR SELECT USING ((bucket_id = 'avatars'::text));
--
-- Name: buckets; Type: ROW SECURITY; Schema: storage; Owner: -
--
ALTER TABLE storage.buckets ENABLE ROW LEVEL SECURITY;
--
-- Name: migrations; Type: ROW SECURITY; Schema: storage; Owner: -
--
ALTER TABLE storage.migrations ENABLE ROW LEVEL SECURITY;
--
-- Name: objects; Type: ROW SECURITY; Schema: storage; Owner: -
--
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
--
-- Name: supabase_realtime; Type: PUBLICATION; Schema: -; Owner: -
--
CREATE PUBLICATION supabase_realtime WITH (publish = 'insert, update, delete, truncate');
--
-- Name: supabase_realtime Profile; Type: PUBLICATION TABLE; Schema: public; Owner: -
--
ALTER PUBLICATION supabase_realtime ADD TABLE ONLY public."Profile";
--
-- Name: issue_pg_cron_access; Type: EVENT TRIGGER; Schema: -; Owner: -
--
CREATE EVENT TRIGGER issue_pg_cron_access ON ddl_command_end
WHEN TAG IN ('CREATE SCHEMA')
EXECUTE FUNCTION extensions.grant_pg_cron_access();
--
-- Name: issue_pg_net_access; Type: EVENT TRIGGER; Schema: -; Owner: -
--
CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end
WHEN TAG IN ('CREATE EXTENSION')
EXECUTE FUNCTION extensions.grant_pg_net_access();
--
-- Name: pgrst_ddl_watch; Type: EVENT TRIGGER; Schema: -; Owner: -
--
CREATE EVENT TRIGGER pgrst_ddl_watch ON ddl_command_end
EXECUTE FUNCTION extensions.pgrst_ddl_watch();
--
-- Name: pgrst_drop_watch; Type: EVENT TRIGGER; Schema: -; Owner: -
--
CREATE EVENT TRIGGER pgrst_drop_watch ON sql_drop
EXECUTE FUNCTION extensions.pgrst_drop_watch();
--
-- PostgreSQL database dump complete
--
--
-- Dbmate schema migrations
--
================================================
FILE: data/db/row_level_security_polices.csv
================================================
schemaname,tablename,policyname,permissive,roles,cmd,qual,with_check
public,Profile,Public profiles are viewable by everyone.,PERMISSIVE,{public},SELECT,true,
public,Profile,Users can insert their own profile.,PERMISSIVE,{public},INSERT,,(auth.uid() = id)
public,Profile,Users can update own profile.,PERMISSIVE,{public},UPDATE,(auth.uid() = id),
storage,objects,Avatar images are publicly accessible.,PERMISSIVE,{public},SELECT,"(bucket_id = 'avatars'::text)",
storage,objects,Anyone can upload an avatar.,PERMISSIVE,{public},INSERT,,"(bucket_id = 'avatars'::text)"
storage,objects,Anyone can update an avatar.,PERMISSIVE,{public},UPDATE,,"(bucket_id = 'avatars'::text)"
public,Post,All users can view posts,PERMISSIVE,{public},SELECT,true,
public,Post,Only authenticated users can create posts,PERMISSIVE,{public},INSERT,,"(auth.role() = 'authenticated'::text)"
public,Post,Users can delete their own posts,PERMISSIVE,{public},DELETE,"(auth.uid() = ""profileId"")",
public,Post,Users can edit their own posts,PERMISSIVE,{public},UPDATE,"(auth.uid() = ""profileId"")","(auth.uid() = ""profileId"")"
public,Comment,Everyone can view comments,PERMISSIVE,{public},SELECT,true,
public,Comment,Only authenticated users can comment,PERMISSIVE,{public},INSERT,,"(auth.role() = 'authenticated'::text)"
public,Comment,User can edit their own comments,PERMISSIVE,{public},UPDATE,"(auth.uid() = ""profileId"")","(auth.uid() = ""profileId"")"
public,Comment,Users can delete their own comments,PERMISSIVE,{public},DELETE,"(auth.uid() = ""profileId"")",
public,Vote,Everyone can view votes,PERMISSIVE,{public},SELECT,true,
public,Vote,Only authenticated users can vote,PERMISSIVE,{public},INSERT,,"(auth.role() = 'authenticated'::text)"
public,Vote,Users can change their vote,PERMISSIVE,{public},UPDATE,"(auth.uid() = ""profileId"")","(auth.uid() = ""profileId"")"
public,Vote,Users can delete their own votes,PERMISSIVE,{public},DELETE,"(auth.uid() = ""profileId"")",
================================================
FILE: data/db/schema.sql
================================================
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: auth; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA auth;
--
-- Name: extensions; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA extensions;
--
-- Name: pg_graphql; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pg_graphql WITH SCHEMA public;
--
-- Name: EXTENSION pg_graphql; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pg_graphql IS 'GraphQL support';
--
-- Name: graphql_public; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA graphql_public;
--
-- Name: pg_net; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pg_net WITH SCHEMA extensions;
--
-- Name: EXTENSION pg_net; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pg_net IS 'Async HTTP';
--
-- Name: pgbouncer; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA pgbouncer;
--
-- Name: realtime; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA realtime;
--
-- Name: storage; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA storage;
--
-- Name: supabase_functions; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA supabase_functions;
--
-- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA extensions;
--
-- Name: EXTENSION pg_stat_statements; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pg_stat_statements IS 'track planning and execution statistics of all SQL statements executed';
--
-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA extensions;
--
-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
--
-- Name: pgjwt; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pgjwt WITH SCHEMA extensions;
--
-- Name: EXTENSION pgjwt; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pgjwt IS 'JSON Web Token API for Postgresql';
--
-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA extensions;
--
-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)';
--
-- Name: direction; Type: TYPE; Schema: public; Owner: -
--
CREATE TYPE public.direction AS ENUM (
'UP',
'DOWN'
);
--
-- Name: action; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.action AS ENUM (
'INSERT',
'UPDATE',
'DELETE',
'TRUNCATE',
'ERROR'
);
--
-- Name: equality_op; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.equality_op AS ENUM (
'eq',
'neq',
'lt',
'lte',
'gt',
'gte'
);
--
-- Name: user_defined_filter; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.user_defined_filter AS (
column_name text,
op realtime.equality_op,
value text
);
--
-- Name: wal_column; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.wal_column AS (
name text,
type text,
value jsonb,
is_pkey boolean,
is_selectable boolean
);
--
-- Name: wal_rls; Type: TYPE; Schema: realtime; Owner: -
--
CREATE TYPE realtime.wal_rls AS (
wal jsonb,
is_rls_enabled boolean,
subscription_ids uuid[],
errors text[]
);
--
-- Name: email(); Type: FUNCTION; Schema: auth; Owner: -
--
CREATE FUNCTION auth.email() RETURNS text
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.email', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'email')
)::text
$$;
--
-- Name: role(); Type: FUNCTION; Schema: auth; Owner: -
--
CREATE FUNCTION auth.role() RETURNS text
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.role', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role')
)::text
$$;
--
-- Name: uid(); Type: FUNCTION; Schema: auth; Owner: -
--
CREATE FUNCTION auth.uid() RETURNS uuid
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.sub', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
)::uuid
$$;
--
-- Name: grant_pg_cron_access(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.grant_pg_cron_access() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
schema_is_cron bool;
BEGIN
schema_is_cron = (
SELECT n.nspname = 'cron'
FROM pg_event_trigger_ddl_commands() AS ev
LEFT JOIN pg_catalog.pg_namespace AS n
ON ev.objid = n.oid
);
IF schema_is_cron
THEN
grant usage on schema cron to postgres with grant option;
alter default privileges in schema cron grant all on tables to postgres with grant option;
alter default privileges in schema cron grant all on functions to postgres with grant option;
alter default privileges in schema cron grant all on sequences to postgres with grant option;
alter default privileges for user supabase_admin in schema cron grant all
on sequences to postgres with grant option;
alter default privileges for user supabase_admin in schema cron grant all
on tables to postgres with grant option;
alter default privileges for user supabase_admin in schema cron grant all
on functions to postgres with grant option;
grant all privileges on all tables in schema cron to postgres with grant option;
END IF;
END;
$$;
--
-- Name: FUNCTION grant_pg_cron_access(); Type: COMMENT; Schema: extensions; Owner: -
--
COMMENT ON FUNCTION extensions.grant_pg_cron_access() IS 'Grants access to pg_cron';
--
-- Name: grant_pg_graphql_access(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.grant_pg_graphql_access() RETURNS event_trigger
LANGUAGE plpgsql
AS $_$
DECLARE
func_is_graphql_resolve bool;
BEGIN
func_is_graphql_resolve = (
SELECT n.proname = 'resolve'
FROM pg_event_trigger_ddl_commands() AS ev
LEFT JOIN pg_catalog.pg_proc AS n
ON ev.objid = n.oid
);
IF func_is_graphql_resolve
THEN
grant usage on schema graphql to postgres, anon, authenticated, service_role;
grant all on function graphql.resolve to postgres, anon, authenticated, service_role;
alter default privileges in schema graphql grant all on tables to postgres, anon, authenticated, service_role;
alter default privileges in schema graphql grant all on functions to postgres, anon, authenticated, service_role;
alter default privileges in schema graphql grant all on sequences to postgres, anon, authenticated, service_role;
create or replace function graphql_public.graphql(
"operationName" text default null,
query text default null,
variables jsonb default null,
extensions jsonb default null
)
returns jsonb
language sql
as $$
SELECT graphql.resolve(query, coalesce(variables, '{}'));
$$;
grant select on graphql.field, graphql.type, graphql.enum_value to postgres, anon, authenticated, service_role;
grant execute on function graphql.resolve to postgres, anon, authenticated, service_role;
END IF;
END;
$_$;
--
-- Name: FUNCTION grant_pg_graphql_access(); Type: COMMENT; Schema: extensions; Owner: -
--
COMMENT ON FUNCTION extensions.grant_pg_graphql_access() IS 'Grants access to pg_graphql';
--
-- Name: grant_pg_net_access(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.grant_pg_net_access() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_event_trigger_ddl_commands() AS ev
JOIN pg_extension AS ext
ON ev.objid = ext.oid
WHERE ext.extname = 'pg_net'
)
THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_collect_response(request_id bigint, async boolean) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_collect_response(request_id bigint, async boolean) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_collect_response(request_id bigint, async boolean) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_collect_response(request_id bigint, async boolean) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END;
$$;
--
-- Name: FUNCTION grant_pg_net_access(); Type: COMMENT; Schema: extensions; Owner: -
--
COMMENT ON FUNCTION extensions.grant_pg_net_access() IS 'Grants access to pg_net';
--
-- Name: notify_api_restart(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.notify_api_restart() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
BEGIN
NOTIFY pgrst, 'reload schema';
END;
$$;
--
-- Name: FUNCTION notify_api_restart(); Type: COMMENT; Schema: extensions; Owner: -
--
COMMENT ON FUNCTION extensions.notify_api_restart() IS 'Sends a notification to the API to restart. If your database schema has changed, this is required so that Supabase can rebuild the relationships.';
--
-- Name: pgrst_ddl_watch(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.pgrst_ddl_watch() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
cmd record;
BEGIN
FOR cmd IN SELECT * FROM pg_event_trigger_ddl_commands()
LOOP
IF cmd.command_tag IN (
'CREATE SCHEMA', 'ALTER SCHEMA'
, 'CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO', 'ALTER TABLE'
, 'CREATE FOREIGN TABLE', 'ALTER FOREIGN TABLE'
, 'CREATE VIEW', 'ALTER VIEW'
, 'CREATE MATERIALIZED VIEW', 'ALTER MATERIALIZED VIEW'
, 'CREATE FUNCTION', 'ALTER FUNCTION'
, 'CREATE TRIGGER'
, 'CREATE TYPE', 'ALTER TYPE'
, 'CREATE RULE'
, 'COMMENT'
)
-- don't notify in case of CREATE TEMP table or other objects created on pg_temp
AND cmd.schema_name is distinct from 'pg_temp'
THEN
NOTIFY pgrst, 'reload schema';
END IF;
END LOOP;
END; $$;
--
-- Name: pgrst_drop_watch(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.pgrst_drop_watch() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
LOOP
IF obj.object_type IN (
'schema'
, 'table'
, 'foreign table'
, 'view'
, 'materialized view'
, 'function'
, 'trigger'
, 'type'
, 'rule'
)
AND obj.is_temporary IS false -- no pg_temp objects
THEN
NOTIFY pgrst, 'reload schema';
END IF;
END LOOP;
END; $$;
--
-- Name: set_graphql_placeholder(); Type: FUNCTION; Schema: extensions; Owner: -
--
CREATE FUNCTION extensions.set_graphql_placeholder() RETURNS event_trigger
LANGUAGE plpgsql
AS $_$
DECLARE
graphql_is_dropped bool;
BEGIN
graphql_is_dropped = (
SELECT ev.schema_name = 'graphql_public'
FROM pg_event_trigger_dropped_objects() AS ev
WHERE ev.schema_name = 'graphql_public'
);
IF graphql_is_dropped
THEN
create or replace function graphql_public.graphql(
"operationName" text default null,
query text default null,
variables jsonb default null,
extensions jsonb default null
)
returns jsonb
language plpgsql
as $$
DECLARE
server_version float;
BEGIN
server_version = (SELECT (SPLIT_PART((select version()), ' ', 2))::float);
IF server_version >= 14 THEN
RETURN jsonb_build_object(
'data', null::jsonb,
'errors', array['pg_graphql extension is not enabled.']
);
ELSE
RETURN jsonb_build_object(
'data', null::jsonb,
'errors', array['pg_graphql is only available on projects running Postgres 14 onwards.']
);
END IF;
END;
$$;
END IF;
END;
$_$;
--
-- Name: FUNCTION set_graphql_placeholder(); Type: COMMENT; Schema: extensions; Owner: -
--
COMMENT ON FUNCTION extensions.set_graphql_placeholder() IS 'Reintroduces placeholder function for graphql_public.graphql';
--
-- Name: get_auth(text); Type: FUNCTION; Schema: pgbouncer; Owner: -
--
CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(username text, password text)
LANGUAGE plpgsql SECURITY DEFINER
AS $$
BEGIN
RAISE WARNING 'PgBouncer auth request: %', p_usename;
RETURN QUERY
SELECT usename::TEXT, passwd::TEXT FROM pg_catalog.pg_shadow
WHERE usename = p_usename;
END;
$$;
--
-- Name: graphql(text, text, jsonb, jsonb); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.graphql("operationName" text DEFAULT NULL::text, query text DEFAULT NULL::text, variables jsonb DEFAULT NULL::jsonb, extensions jsonb DEFAULT NULL::jsonb) RETURNS jsonb
LANGUAGE sql
AS $$
select graphql.resolve(query, coalesce(variables, '{}'));
$$;
--
-- Name: handle_new_user(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.handle_new_user() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
AS $$
begin
insert into public."Profile" (id, "avatarUrl", username)
values (new.id, 'https://www.gravatar.com/avatar/' || md5(new.email) || '?d=mp', split_part(new.email, '@', 1) || '-' || floor(random() * 10000));
return new;
end;
$$;
--
-- Name: update_vote_counts(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.update_vote_counts() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
AS $$
BEGIN
WITH r AS (
SELECT
coalesce("Vote"."postId", "Post".id) AS "postId",
count(1) "voteTotal",
count(1) FILTER (WHERE direction = 'UP') "upVoteTotal",
count(1) FILTER (WHERE direction = 'DOWN') "downVoteTotal",
coalesce(sum(
CASE WHEN direction = 'UP' THEN
1
WHEN direction = 'DOWN' THEN
-1
ELSE
0
END), 0) "voteDelta",
round(coalesce((sum(
CASE WHEN direction = 'UP' THEN
1
WHEN direction = 'DOWN' THEN
-1
ELSE
0
END ) - 1) / (DATE_PART('hour', now() - max("Vote"."createdAt")) + 2) ^ 1.8 * 100000, -2147483648)::numeric, 0) AS "score",
rank() OVER (ORDER BY round(coalesce((sum( CASE WHEN direction = 'UP' THEN
1
WHEN direction = 'DOWN' THEN
-1
ELSE
0
END) - 1) / (DATE_PART('hour', now() - max("Vote"."createdAt")) + 2) ^ 1.8 * 100000, -2147483648)::numeric, 0)
DESC,
"Post"."createdAt" DESC,
"Post".title ASC) "voteRank"
FROM
"Vote"
RIGHT JOIN "Post" ON "Vote"."postId" = "Post".id
GROUP BY
"Post".id,
"Vote"."postId"
)
UPDATE
public. "Post"
SET
"upVoteTotal" = r. "upVoteTotal",
"downVoteTotal" = r. "downVoteTotal",
"voteTotal" = r. "voteTotal",
"voteDelta" = r. "voteDelta",
"voteRank" = r. "voteRank",
"score" = r. "score"
FROM
r
WHERE
r."postId" = public. "Post".id;
RETURN new;
END;
$$;
--
-- Name: apply_rls(jsonb, integer); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.apply_rls(wal jsonb, max_record_bytes integer DEFAULT (1024 * 1024)) RETURNS SETOF realtime.wal_rls
LANGUAGE plpgsql
AS $$
declare
-- Regclass of the table e.g. public.notes
entity_ regclass = (quote_ident(wal ->> 'schema') || '.' || quote_ident(wal ->> 'table'))::regclass;
-- I, U, D, T: insert, update ...
action realtime.action = (
case wal ->> 'action'
when 'I' then 'INSERT'
when 'U' then 'UPDATE'
when 'D' then 'DELETE'
else 'ERROR'
end
);
-- Is row level security enabled for the table
is_rls_enabled bool = relrowsecurity from pg_class where oid = entity_;
subscriptions realtime.subscription[] = array_agg(subs)
from
realtime.subscription subs
where
subs.entity = entity_;
-- Subscription vars
roles regrole[] = array_agg(distinct us.claims_role)
from
unnest(subscriptions) us;
working_role regrole;
claimed_role regrole;
claims jsonb;
subscription_id uuid;
subscription_has_access bool;
visible_to_subscription_ids uuid[] = '{}';
-- structured info for wal's columns
columns realtime.wal_column[];
-- previous identity values for update/delete
old_columns realtime.wal_column[];
error_record_exceeds_max_size boolean = octet_length(wal::text) > max_record_bytes;
-- Primary jsonb output for record
output jsonb;
begin
perform set_config('role', null, true);
columns =
array_agg(
(
x->>'name',
x->>'type',
realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),
(pks ->> 'name') is not null,
true
)::realtime.wal_column
)
from
jsonb_array_elements(wal -> 'columns') x
left join jsonb_array_elements(wal -> 'pk') pks
on (x ->> 'name') = (pks ->> 'name');
old_columns =
array_agg(
(
x->>'name',
x->>'type',
realtime.cast((x->'value') #>> '{}', (x->>'type')::regtype),
(pks ->> 'name') is not null,
true
)::realtime.wal_column
)
from
jsonb_array_elements(wal -> 'identity') x
left join jsonb_array_elements(wal -> 'pk') pks
on (x ->> 'name') = (pks ->> 'name');
for working_role in select * from unnest(roles) loop
-- Update `is_selectable` for columns and old_columns
columns =
array_agg(
(
c.name,
c.type,
c.value,
c.is_pkey,
pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')
)::realtime.wal_column
)
from
unnest(columns) c;
old_columns =
array_agg(
(
c.name,
c.type,
c.value,
c.is_pkey,
pg_catalog.has_column_privilege(working_role, entity_, c.name, 'SELECT')
)::realtime.wal_column
)
from
unnest(old_columns) c;
if action <> 'DELETE' and count(1) = 0 from unnest(columns) c where c.is_pkey then
return next (
null,
is_rls_enabled,
-- subscriptions is already filtered by entity
(select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),
array['Error 400: Bad Request, no primary key']
)::realtime.wal_rls;
-- The claims role does not have SELECT permission to the primary key of entity
elsif action <> 'DELETE' and sum(c.is_selectable::int) <> count(1) from unnest(columns) c where c.is_pkey then
return next (
null,
is_rls_enabled,
(select array_agg(s.subscription_id) from unnest(subscriptions) as s where claims_role = working_role),
array['Error 401: Unauthorized']
)::realtime.wal_rls;
else
output = jsonb_build_object(
'schema', wal ->> 'schema',
'table', wal ->> 'table',
'type', action,
'commit_timestamp', to_char(
(wal ->> 'timestamp')::timestamptz,
'YYYY-MM-DD"T"HH24:MI:SS"Z"'
),
'columns', (
select
jsonb_agg(
jsonb_build_object(
'name', pa.attname,
'type', pt.typname
)
order by pa.attnum asc
)
from
pg_attribute pa
join pg_type pt
on pa.atttypid = pt.oid
where
attrelid = entity_
and attnum > 0
and pg_catalog.has_column_privilege(working_role, entity_, pa.attname, 'SELECT')
)
)
-- Add "record" key for insert and update
|| case
when error_record_exceeds_max_size then jsonb_build_object('record', '{}'::jsonb)
when action in ('INSERT', 'UPDATE') then
jsonb_build_object(
'record',
(select jsonb_object_agg((c).name, (c).value) from unnest(columns) c where (c).is_selectable)
)
else '{}'::jsonb
end
-- Add "old_record" key for update and delete
|| case
when error_record_exceeds_max_size then jsonb_build_object('old_record', '{}'::jsonb)
when action in ('UPDATE', 'DELETE') then
jsonb_build_object(
'old_record',
(select jsonb_object_agg((c).name, (c).value) from unnest(old_columns) c where (c).is_selectable)
)
else '{}'::jsonb
end;
-- Create the prepared statement
if is_rls_enabled and action <> 'DELETE' then
if (select 1 from pg_prepared_statements where name = 'walrus_rls_stmt' limit 1) > 0 then
deallocate walrus_rls_stmt;
end if;
execute realtime.build_prepared_statement_sql('walrus_rls_stmt', entity_, columns);
end if;
visible_to_subscription_ids = '{}';
for subscription_id, claims in (
select
subs.subscription_id,
subs.claims
from
unnest(subscriptions) subs
where
subs.entity = entity_
and subs.claims_role = working_role
and realtime.is_visible_through_filters(columns, subs.filters)
) loop
if not is_rls_enabled or action = 'DELETE' then
visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;
else
-- Check if RLS allows the role to see the record
perform
set_config('role', working_role::text, true),
set_config('request.jwt.claims', claims::text, true);
execute 'execute walrus_rls_stmt' into subscription_has_access;
if subscription_has_access then
visible_to_subscription_ids = visible_to_subscription_ids || subscription_id;
end if;
end if;
end loop;
perform set_config('role', null, true);
return next (
output,
is_rls_enabled,
visible_to_subscription_ids,
case
when error_record_exceeds_max_size then array['Error 413: Payload Too Large']
else '{}'
end
)::realtime.wal_rls;
end if;
end loop;
perform set_config('role', null, true);
end;
$$;
--
-- Name: build_prepared_statement_sql(text, regclass, realtime.wal_column[]); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.build_prepared_statement_sql(prepared_statement_name text, entity regclass, columns realtime.wal_column[]) RETURNS text
LANGUAGE sql
AS $$
/*
Builds a sql string that, if executed, creates a prepared statement to
tests retrive a row from *entity* by its primary key columns.
Example
select realtime.build_prepared_statment_sql('public.notes', '{"id"}'::text[], '{"bigint"}'::text[])
*/
select
'prepare ' || prepared_statement_name || ' as
select
exists(
select
1
from
' || entity || '
where
' || string_agg(quote_ident(pkc.name) || '=' || quote_nullable(pkc.value #>> '{}') , ' and ') || '
)'
from
unnest(columns) pkc
where
pkc.is_pkey
group by
entity
$$;
--
-- Name: cast(text, regtype); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime."cast"(val text, type_ regtype) RETURNS jsonb
LANGUAGE plpgsql IMMUTABLE
AS $$
declare
res jsonb;
begin
execute format('select to_jsonb(%L::'|| type_::text || ')', val) into res;
return res;
end
$$;
--
-- Name: check_equality_op(realtime.equality_op, regtype, text, text); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.check_equality_op(op realtime.equality_op, type_ regtype, val_1 text, val_2 text) RETURNS boolean
LANGUAGE plpgsql IMMUTABLE
AS $$
/*
Casts *val_1* and *val_2* as type *type_* and check the *op* condition for truthiness
*/
declare
op_symbol text = (
case
when op = 'eq' then '='
when op = 'neq' then '!='
when op = 'lt' then '<'
when op = 'lte' then '<='
when op = 'gt' then '>'
when op = 'gte' then '>='
else 'UNKNOWN OP'
end
);
res boolean;
begin
execute format('select %L::'|| type_::text || ' ' || op_symbol || ' %L::'|| type_::text, val_1, val_2) into res;
return res;
end;
$$;
--
-- Name: is_visible_through_filters(realtime.wal_column[], realtime.user_defined_filter[]); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.is_visible_through_filters(columns realtime.wal_column[], filters realtime.user_defined_filter[]) RETURNS boolean
LANGUAGE sql IMMUTABLE
AS $$
/*
Should the record be visible (true) or filtered out (false) after *filters* are applied
*/
select
-- Default to allowed when no filters present
coalesce(
sum(
realtime.check_equality_op(
op:=f.op,
type_:=col.type::regtype,
-- cast jsonb to text
val_1:=col.value #>> '{}',
val_2:=f.value
)::int
) = count(1),
true
)
from
unnest(filters) f
join unnest(columns) col
on f.column_name = col.name;
$$;
--
-- Name: quote_wal2json(regclass); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.quote_wal2json(entity regclass) RETURNS text
LANGUAGE sql IMMUTABLE STRICT
AS $$
select
(
select string_agg('' || ch,'')
from unnest(string_to_array(nsp.nspname::text, null)) with ordinality x(ch, idx)
where
not (x.idx = 1 and x.ch = '"')
and not (
x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)
and x.ch = '"'
)
)
|| '.'
|| (
select string_agg('' || ch,'')
from unnest(string_to_array(pc.relname::text, null)) with ordinality x(ch, idx)
where
not (x.idx = 1 and x.ch = '"')
and not (
x.idx = array_length(string_to_array(nsp.nspname::text, null), 1)
and x.ch = '"'
)
)
from
pg_class pc
join pg_namespace nsp
on pc.relnamespace = nsp.oid
where
pc.oid = entity
$$;
--
-- Name: subscription_check_filters(); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.subscription_check_filters() RETURNS trigger
LANGUAGE plpgsql
AS $$
/*
Validates that the user defined filters for a subscription:
- refer to valid columns that the claimed role may access
- values are coercable to the correct column type
*/
declare
col_names text[] = coalesce(
array_agg(c.column_name order by c.ordinal_position),
'{}'::text[]
)
from
information_schema.columns c
where
format('%I.%I', c.table_schema, c.table_name)::regclass = new.entity
and pg_catalog.has_column_privilege(
(new.claims ->> 'role'),
format('%I.%I', c.table_schema, c.table_name)::regclass,
c.column_name,
'SELECT'
);
filter realtime.user_defined_filter;
col_type regtype;
begin
for filter in select * from unnest(new.filters) loop
-- Filtered column is valid
if not filter.column_name = any(col_names) then
raise exception 'invalid column for filter %', filter.column_name;
end if;
-- Type is sanitized and safe for string interpolation
col_type = (
select atttypid::regtype
from pg_catalog.pg_attribute
where attrelid = new.entity
and attname = filter.column_name
);
if col_type is null then
raise exception 'failed to lookup type for column %', filter.column_name;
end if;
-- raises an exception if value is not coercable to type
perform realtime.cast(filter.value, col_type);
end loop;
-- Apply consistent order to filters so the unique constraint on
-- (subscription_id, entity, filters) can't be tricked by a different filter order
new.filters = coalesce(
array_agg(f order by f.column_name, f.op, f.value),
'{}'
) from unnest(new.filters) f;
return new;
end;
$$;
--
-- Name: to_regrole(text); Type: FUNCTION; Schema: realtime; Owner: -
--
CREATE FUNCTION realtime.to_regrole(role_name text) RETURNS regrole
LANGUAGE sql IMMUTABLE
AS $$ select role_name::regrole $$;
--
-- Name: extension(text); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.extension(name text) RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
_filename text;
BEGIN
select string_to_array(name, '/') into _parts;
select _parts[array_length(_parts,1)] into _filename;
-- @todo return the last part instead of 2
return split_part(_filename, '.', 2);
END
$$;
--
-- Name: filename(text); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.filename(name text) RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[array_length(_parts,1)];
END
$$;
--
-- Name: foldername(text); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.foldername(name text) RETURNS text[]
LANGUAGE plpgsql
AS $$
DECLARE
_parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[1:array_length(_parts,1)-1];
END
$$;
--
-- Name: get_size_by_bucket(); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.get_size_by_bucket() RETURNS TABLE(size bigint, bucket_id text)
LANGUAGE plpgsql
AS $$
BEGIN
return query
select sum((metadata->>'size')::int) as size, obj.bucket_id
from "storage".objects as obj
group by obj.bucket_id;
END
$$;
--
-- Name: search(text, text, integer, integer, integer, text, text, text); Type: FUNCTION; Schema: storage; Owner: -
--
CREATE FUNCTION storage.search(prefix text, bucketname text, limits integer DEFAULT 100, levels integer DEFAULT 1, offsets integer DEFAULT 0, search text DEFAULT ''::text, sortcolumn text DEFAULT 'name'::text, sortorder text DEFAULT 'asc'::text) RETURNS TABLE(name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb)
LANGUAGE plpgsql STABLE
AS $_$
declare
v_order_by text;
v_sort_order text;
begin
case
when sortcolumn = 'name' then
v_order_by = 'name';
when sortcolumn = 'updated_at' then
v_order_by = 'updated_at';
when sortcolumn = 'created_at' then
v_order_by = 'created_at';
when sortcolumn = 'last_accessed_at' then
v_order_by = 'last_accessed_at';
else
v_order_by = 'name';
end case;
case
when sortorder = 'asc' then
v_sort_order = 'asc';
when sortorder = 'desc' then
v_sort_order = 'desc';
else
v_sort_order = 'asc';
end case;
v_order_by = v_order_by || ' ' || v_sort_order;
return query execute
'with folders as (
select path_tokens[$1] as folder
from storage.objects
where objects.name ilike $2 || $3 || ''%''
and bucket_id = $4
and array_length(regexp_split_to_array(objects.name, ''/''), 1) <> $1
group by folder
order by folder ' || v_sort_order || '
)
(select folder as "name",
null as id,
null as updated_at,
null as created_at,
null as last_accessed_at,
null as metadata from folders)
union all
(select path_tokens[$1] as "name",
id,
updated_at,
created_at,
last_accessed_at,
metadata
from storage.objects
where objects.name ilike $2 || $3 || ''%''
and bucket_id = $4
and array_length(regexp_split_to_array(objects.name, ''/''), 1) = $1
order by ' || v_order_by || ')
limit $5
offset $6' using levels, prefix, search, bucketname, limits, offsets;
end;
$_$;
--
-- Name: http_request(); Type: FUNCTION; Schema: supabase_functions; Owner: -
--
CREATE FUNCTION supabase_functions.http_request() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
SET search_path TO 'supabase_functions'
AS $$
DECLARE
request_id bigint;
payload jsonb;
url text := TG_ARGV[0]::text;
method text := TG_ARGV[1]::text;
headers jsonb DEFAULT '{}'::jsonb;
params jsonb DEFAULT '{}'::jsonb;
timeout_ms integer DEFAULT 1000;
BEGIN
IF url IS NULL OR url = 'null' THEN
RAISE EXCEPTION 'url argument is missing';
END IF;
IF method IS NULL OR method = 'null' THEN
RAISE EXCEPTION 'method argument is missing';
END IF;
IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
headers = '{"Content-Type": "application/json"}'::jsonb;
ELSE
headers = TG_ARGV[2]::jsonb;
END IF;
IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
params = '{}'::jsonb;
ELSE
params = TG_ARGV[3]::jsonb;
END IF;
IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
timeout_ms = 1000;
ELSE
timeout_ms = TG_ARGV[4]::integer;
END IF;
CASE
WHEN method = 'GET' THEN
SELECT http_get INTO request_id FROM net.http_get(
url,
params,
headers,
timeout_ms
);
WHEN method = 'POST' THEN
payload = jsonb_build_object(
'old_record', OLD,
'record', NEW,
'type', TG_OP,
'table', TG_TABLE_NAME,
'schema', TG_TABLE_SCHEMA
);
SELECT http_post INTO request_id FROM net.http_post(
url,
payload,
params,
headers,
timeout_ms
);
ELSE
RAISE EXCEPTION 'method argument % is invalid', method;
END CASE;
INSERT INTO supabase_functions.hooks
(hook_table_id, hook_name, request_id)
VALUES
(TG_RELID, TG_NAME, request_id);
RETURN NEW;
END
$$;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: audit_log_entries; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.audit_log_entries (
instance_id uuid,
id uuid NOT NULL,
payload json,
created_at timestamp with time zone
);
--
-- Name: TABLE audit_log_entries; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.audit_log_entries IS 'Auth: Audit trail for user actions.';
--
-- Name: identities; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.identities (
id text NOT NULL,
user_id uuid NOT NULL,
identity_data jsonb NOT NULL,
provider text NOT NULL,
last_sign_in_at timestamp with time zone,
created_at timestamp with time zone,
updated_at timestamp with time zone
);
--
-- Name: TABLE identities; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.identities IS 'Auth: Stores identities associated to a user.';
--
-- Name: instances; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.instances (
id uuid NOT NULL,
uuid uuid,
raw_base_config text,
created_at timestamp with time zone,
updated_at timestamp with time zone
);
--
-- Name: TABLE instances; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.instances IS 'Auth: Manages users across multiple sites.';
--
-- Name: refresh_tokens; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.refresh_tokens (
instance_id uuid,
id bigint NOT NULL,
token character varying(255),
user_id character varying(255),
revoked boolean,
created_at timestamp with time zone,
updated_at timestamp with time zone,
parent character varying(255)
);
--
-- Name: TABLE refresh_tokens; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.refresh_tokens IS 'Auth: Store of tokens used to refresh JWT tokens once they expire.';
--
-- Name: refresh_tokens_id_seq; Type: SEQUENCE; Schema: auth; Owner: -
--
CREATE SEQUENCE auth.refresh_tokens_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: refresh_tokens_id_seq; Type: SEQUENCE OWNED BY; Schema: auth; Owner: -
--
ALTER SEQUENCE auth.refresh_tokens_id_seq OWNED BY auth.refresh_tokens.id;
--
-- Name: schema_migrations; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.schema_migrations (
version character varying(255) NOT NULL
);
--
-- Name: TABLE schema_migrations; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.schema_migrations IS 'Auth: Manages updates to the auth system.';
--
-- Name: users; Type: TABLE; Schema: auth; Owner: -
--
CREATE TABLE auth.users (
instance_id uuid,
id uuid NOT NULL,
aud character varying(255),
role character varying(255),
email character varying(255),
encrypted_password character varying(255),
email_confirmed_at timestamp with time zone,
invited_at timestamp with time zone,
confirmation_token character varying(255),
confirmation_sent_at timestamp with time zone,
recovery_token character varying(255),
recovery_sent_at timestamp with time zone,
email_change_token_new character varying(255),
email_change character varying(255),
email_change_sent_at timestamp with time zone,
last_sign_in_at timestamp with time zone,
raw_app_meta_data jsonb,
raw_user_meta_data jsonb,
is_super_admin boolean,
created_at timestamp with time zone,
updated_at timestamp with time zone,
phone character varying(15) DEFAULT NULL::character varying,
phone_confirmed_at timestamp with time zone,
phone_change character varying(15) DEFAULT ''::character varying,
phone_change_token character varying(255) DEFAULT ''::character varying,
phone_change_sent_at timestamp with time zone,
confirmed_at timestamp with time zone GENERATED ALWAYS AS (LEAST(email_confirmed_at, phone_confirmed_at)) STORED,
email_change_token_current character varying(255) DEFAULT ''::character varying,
email_change_confirm_status smallint DEFAULT 0,
banned_until timestamp with time zone,
reauthentication_token character varying(255) DEFAULT ''::character varying,
reauthentication_sent_at timestamp with time zone,
CONSTRAINT users_email_change_confirm_status_check CHECK (((email_change_confirm_status >= 0) AND (email_change_confirm_status <= 2)))
);
--
-- Name: TABLE users; Type: COMMENT; Schema: auth; Owner: -
--
COMMENT ON TABLE auth.users IS 'Auth: Stores user login data within a secure schema.';
--
-- Name: Comment; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Comment" (
id bigint NOT NULL,
"createdAt" timestamp with time zone DEFAULT now(),
"updatedAt" timestamp with time zone,
message text NOT NULL,
"profileId" uuid NOT NULL,
"postId" bigint NOT NULL
);
--
-- Name: TABLE "Comment"; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public."Comment" IS '@graphql({"totalCount": {"enabled": true}})';
--
-- Name: Comment_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
ALTER TABLE public."Comment" ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public."Comment_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- Name: Vote; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Vote" (
id bigint NOT NULL,
"createdAt" timestamp with time zone DEFAULT now(),
"updatedAt" timestamp with time zone,
"profileId" uuid NOT NULL,
"postId" bigint NOT NULL,
direction public.direction NOT NULL
);
--
-- Name: TABLE "Vote"; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public."Vote" IS '@graphql({"totalCount": {"enabled": true}})';
--
-- Name: DownVote_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
ALTER TABLE public."Vote" ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public."DownVote_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- Name: Post; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Post" (
id bigint NOT NULL,
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
"updatedAt" timestamp with time zone,
title text NOT NULL,
url text NOT NULL,
"profileId" uuid NOT NULL,
"upVoteTotal" integer DEFAULT 0 NOT NULL,
"downVoteTotal" integer DEFAULT 0 NOT NULL,
"voteTotal" integer DEFAULT 0 NOT NULL,
"voteRank" integer DEFAULT 1 NOT NULL,
score integer DEFAULT 0,
"voteDelta" integer DEFAULT 0 NOT NULL,
CONSTRAINT post_title_length CHECK ((char_length(title) > 0)),
CONSTRAINT post_url_length CHECK ((char_length(url) > 0))
);
--
-- Name: TABLE "Post"; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public."Post" IS '@graphql({"totalCount": {"enabled": true}})';
--
-- Name: Post_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
ALTER TABLE public."Post" ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public."Post_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- Name: Profile; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Profile" (
id uuid NOT NULL,
updatedat timestamp with time zone,
username text,
"avatarUrl" text,
website text,
bio text,
CONSTRAINT usernamelength CHECK ((char_length(username) >= 3))
);
--
-- Name: TABLE "Profile"; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public."Profile" IS '@graphql({"totalCount": {"enabled": true}})';
--
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.schema_migrations (
version character varying(255) NOT NULL
);
--
-- Name: schema_migrations; Type: TABLE; Schema: realtime; Owner: -
--
CREATE TABLE realtime.schema_migrations (
version bigint NOT NULL,
inserted_at timestamp(0) without time zone
);
--
-- Name: subscription; Type: TABLE; Schema: realtime; Owner: -
--
CREATE TABLE realtime.subscription (
id bigint NOT NULL,
subscription_id uuid NOT NULL,
entity regclass NOT NULL,
filters realtime.user_defined_filter[] DEFAULT '{}'::realtime.user_defined_filter[] NOT NULL,
claims jsonb NOT NULL,
claims_role regrole GENERATED ALWAYS AS (realtime.to_regrole((claims ->> 'role'::text))) STORED NOT NULL,
created_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL
);
--
-- Name: subscription_id_seq; Type: SEQUENCE; Schema: realtime; Owner: -
--
ALTER TABLE realtime.subscription ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME realtime.subscription_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- Name: buckets; Type: TABLE; Schema: storage; Owner: -
--
CREATE TABLE storage.buckets (
id text NOT NULL,
name text NOT NULL,
owner uuid,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now(),
public boolean DEFAULT false
);
--
-- Name: migrations; Type: TABLE; Schema: storage; Owner: -
--
CREATE TABLE storage.migrations (
id integer NOT NULL,
name character varying(100) NOT NULL,
hash character varying(40) NOT NULL,
executed_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP
);
--
-- Name: objects; Type: TABLE; Schema: storage; Owner: -
--
CREATE TABLE storage.objects (
id uuid DEFAULT extensions.uuid_generate_v4() NOT NULL,
bucket_id text,
name text,
owner uuid,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now(),
last_accessed_at timestamp with time zone DEFAULT now(),
metadata jsonb
);
--
-- Name: hooks; Type: TABLE; Schema: supabase_functions; Owner: -
--
CREATE TABLE supabase_functions.hooks (
id bigint NOT NULL,
hook_table_id integer NOT NULL,
hook_name text NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
request_id bigint
);
--
-- Name: TABLE hooks; Type: COMMENT; Schema: supabase_functions; Owner: -
--
COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
--
-- Name: hooks_id_seq; Type: SEQUENCE; Schema: supabase_functions; Owner: -
--
CREATE SEQUENCE supabase_functions.hooks_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: hooks_id_seq; Type: SEQUENCE OWNED BY; Schema: supabase_functions; Owner: -
--
ALTER SEQUENCE supabase_functions.hooks_id_seq OWNED BY supabase_functions.hooks.id;
--
-- Name: migrations; Type: TABLE; Schema: supabase_functions; Owner: -
--
CREATE TABLE supabase_functions.migrations (
version text NOT NULL,
inserted_at timestamp with time zone DEFAULT now() NOT NULL
);
--
-- Name: refresh_tokens id; Type: DEFAULT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.refresh_tokens ALTER COLUMN id SET DEFAULT nextval('auth.refresh_tokens_id_seq'::regclass);
--
-- Name: hooks id; Type: DEFAULT; Schema: supabase_functions; Owner: -
--
ALTER TABLE ONLY supabase_functions.hooks ALTER COLUMN id SET DEFAULT nextval('supabase_functions.hooks_id_seq'::regclass);
--
-- Name: audit_log_entries audit_log_entries_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.audit_log_entries
ADD CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id);
--
-- Name: identities identities_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.identities
ADD CONSTRAINT identities_pkey PRIMARY KEY (provider, id);
--
-- Name: instances instances_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.instances
ADD CONSTRAINT instances_pkey PRIMARY KEY (id);
--
-- Name: refresh_tokens refresh_tokens_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.refresh_tokens
ADD CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id);
--
-- Name: refresh_tokens refresh_tokens_token_unique; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.refresh_tokens
ADD CONSTRAINT refresh_tokens_token_unique UNIQUE (token);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: users users_email_key; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.users
ADD CONSTRAINT users_email_key UNIQUE (email);
--
-- Name: users users_phone_key; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.users
ADD CONSTRAINT users_phone_key UNIQUE (phone);
--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: auth; Owner: -
--
ALTER TABLE ONLY auth.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
--
-- Name: Comment Comment_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Comment"
ADD CONSTRAINT "Comment_pkey" PRIMARY KEY (id);
--
-- Name: Vote DownVote_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Vote"
ADD CONSTRAINT "DownVote_pkey" PRIMARY KEY (id);
--
-- Name: Post Post_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Post"
ADD CONSTRAINT "Post_pkey" PRIMARY KEY (id);
--
-- Name: Post Post_url_key; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Post"
ADD CONSTRAINT "Post_url_key" UNIQUE (url);
--
-- Name: Profile Profile_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Profile"
ADD CONSTRAINT "Profile_pkey" PRIMARY KEY (id);
--
-- Name: Profile Profile_username_key; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Profile"
ADD CONSTRAINT "Profile_username_key" UNIQUE (username);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: subscription pk_subscription; Type: CONSTRAINT; Schema: realtime; Owner: -
--
ALTER TABLE ONLY realtime.subscription
ADD CONSTRAINT pk_subscription PRIMARY KEY (id);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: realtime; Owner: -
--
ALTER TABLE ONLY realtime.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: buckets buckets_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.buckets
ADD CONSTRAINT buckets_pkey PRIMARY KEY (id);
--
-- Name: migrations migrations_name_key; Type: CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.migrations
ADD CONSTRAINT migrations_name_key UNIQUE (name);
--
-- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.migrations
ADD CONSTRAINT migrations_pkey PRIMARY KEY (id);
--
-- Name: objects objects_pkey; Type: CONSTRAINT; Schema: storage; Owner: -
--
ALTER TABLE ONLY storage.objects
ADD CONSTRAINT objects_pkey PRIMARY KEY (id);
--
-- Name: hooks hooks_pkey; Type: CONSTRAINT; Schema: supabase_functions; Owner: -
--
ALTER TABLE ONLY supabase_functions.hooks
ADD CONSTRAINT hooks_pkey PRIMARY KEY (id);
--
-- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: supabase_functions; Owner: -
--
ALTER TABLE ONLY supabase_functions.migrations
ADD CONSTRAINT migrations_pkey PRIMARY KEY (version);
--
-- Name: audit_logs_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id);
--
-- Name: identities_user_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX identities_user_id_idx ON auth.identities USING btree (user_id);
--
-- Name: refresh_tokens_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id);
--
-- Name: refresh_tokens_instance_id_user_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id);
--
-- Name: refresh_tokens_parent_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX refresh_tokens_parent_idx ON auth.refresh_tokens USING btree (parent);
--
-- Name: refresh_tokens_token_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree (token);
--
-- Name: users_instance_id_email_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, lower((email)::text));
--
-- Name: users_instance_id_idx; Type: INDEX; Schema: auth; Owner: -
--
CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id);
--
-- Name: idx_one_vote_per_post; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX idx_one_vote_per_post ON public."Vote" USING btree ("profileId", "postId");
--
-- Name: idx_unique_post_url; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX idx_unique_post_url ON public."Post" USING btree (url);
--
-- Name: ix_realtime_subscription_entity; Type: INDEX; Schema: realtime; Owner: -
--
CREATE INDEX ix_realtime_subscription_entity ON realtime.subscription USING hash (entity);
--
-- Name: subscription_subscription_id_entity_filters_key; Type: INDEX; Schema: realtime; Owner: -
--
CREATE UNIQUE INDEX subscription_subscription_id_entity_filters_key ON realtime.subscription USING btree (subscription_id, entity, filters);
--
-- Name: bname; Type: INDEX; Schema: storage; Owner: -
--
CREATE UNIQUE INDEX bname ON storage.buckets USING btree (name);
--
-- Name: bucketid_objname; Type: INDEX; Schema: storage; Owner: -
--
CREATE UNIQUE INDEX bucketid_objname ON storage.objects USING btree (bucket_id, name);
--
-- Name: name_prefix_search; Type: INDEX; Schema: storage; Owner: -
--
CREATE INDEX name_prefix_search ON storage.objects USING btree (name text_pattern_ops);
--
-- Name: supabase_functions_hooks_h_table_id_h_name_idx; Type: INDEX; Schema: supabase_functions; Owner: -
--
CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
--
-- Name: supabase_functions_hooks_request_id_idx; Type: INDEX; Schema: supabase_functions; Owner: -
--
CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
--
-- Name: users on_auth_user_created; Type: TRIGGER; Schema: auth; Owner: -
--
CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
--
-- Name: Vote on_vote_created; Type: TRIGGER; Schema: public; Owner: -
--
CREATE TRIGGER on_vote_created AFTER INSERT ON public."Vote" FOR EACH ROW EXECUTE FUNCTION public.update_vote_counts();
--
-- Name: Vote on_vote_deleted; Type: TRIGGER; Schema: public; Owner: -
--
CREATE TRIGGER on_vote_deleted AFTER DELETE ON public."Vote" F
gitextract_uc_3wbbe/
├── .editorconfig
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .graphqlrc.yml
├── .husky/
│ ├── .gitignore
│ └── pre-commit
├── .vscode/
│ ├── extensions.json
│ └── settings.json
├── README.md
├── app/
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── gql/
│ │ └── .gitignore
│ ├── lib/
│ │ ├── active-link.tsx
│ │ ├── comment-item.tsx
│ │ ├── container.tsx
│ │ ├── feed-item.tsx
│ │ ├── footer.tsx
│ │ ├── icons.tsx
│ │ ├── loading.tsx
│ │ ├── main-section.tsx
│ │ ├── navigation.tsx
│ │ ├── noop-uuid.ts
│ │ ├── supabase.tsx
│ │ ├── time-ago.ts
│ │ ├── urql.tsx
│ │ └── use-paginated-query.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages/
│ │ ├── _app.tsx
│ │ ├── about.tsx
│ │ ├── account.tsx
│ │ ├── api/
│ │ │ └── graphiql.ts
│ │ ├── comments.tsx
│ │ ├── index.tsx
│ │ ├── item/
│ │ │ └── [postId].tsx
│ │ ├── login.tsx
│ │ ├── logout.tsx
│ │ ├── newest.tsx
│ │ ├── profile/
│ │ │ └── [profileId].tsx
│ │ └── submit.tsx
│ ├── postcss.config.js
│ ├── styles/
│ │ └── globals.css
│ ├── tailwind.config.js
│ └── tsconfig.json
├── data/
│ ├── db/
│ │ ├── backup.sql
│ │ ├── row_level_security_polices.csv
│ │ └── schema.sql
│ ├── seed/
│ │ ├── blog.xml
│ │ ├── blog_posts.csv
│ │ └── comments.csv
│ └── supabase/
│ ├── 00-initial-schema.sql
│ ├── 01-auth-schema.sql
│ ├── 02-storage-schema.sql
│ ├── 03-post-setup.sql
│ ├── 04-public-profiles.sql
│ ├── 05-setup-total-counts.sql
│ ├── 05-setup-user-profile-trigger.sql
│ ├── 06-update-post-vote-counts.sql
│ ├── 07-add-post-title-url-constraints.sql
│ ├── 08-update-post-cascahe-delete-constraints.sql
│ ├── 09-update-all-gravatars.sql
│ └── rls-policies.md
├── graphql/
│ ├── queries/
│ │ ├── feed.graphql
│ │ ├── hasProfileVotes.graphql
│ │ └── rankedFeed.graphql
│ └── schema/
│ └── schema.graphql
├── package.json
├── renovate.json
└── scripts/
└── fetchGraphQLSchema.js
SYMBOL INDEX (168 symbols across 28 files)
FILE: app/lib/active-link.tsx
function ActiveLink (line 9) | function ActiveLink({
FILE: app/lib/comment-item.tsx
function CommentItem (line 38) | function CommentItem(props: {
FILE: app/lib/container.tsx
function Container (line 3) | function Container(props: { children: React.ReactNode }) {
FILE: app/lib/feed-item.tsx
function VoteButtons (line 67) | function VoteButtons(props: {
function FeedItem (line 151) | function FeedItem(props: {
FILE: app/lib/footer.tsx
function Footer (line 4) | function Footer() {
FILE: app/lib/icons.tsx
function CalendarIcon (line 3) | function CalendarIcon(props: { className?: string }) {
function TrashIcon (line 20) | function TrashIcon(props: { className?: string }) {
function CommentIcon (line 36) | function CommentIcon(props: { className?: string }) {
function PointIcon (line 52) | function PointIcon(props: { className?: string }) {
function UserIcon (line 65) | function UserIcon(props: { className?: string }) {
function SupabaseIcon (line 83) | function SupabaseIcon(props: { className?: string; height: number }) {
function ChevronUpIcon (line 127) | function ChevronUpIcon(props: {
function ChevronDownIcon (line 147) | function ChevronDownIcon(props: {
FILE: app/lib/loading.tsx
function Loading (line 4) | function Loading() {
FILE: app/lib/main-section.tsx
function MainSection (line 3) | function MainSection(props: { children: React.ReactNode }) {
FILE: app/lib/navigation.tsx
function Navigation (line 7) | function Navigation() {
FILE: app/lib/supabase.tsx
function SupabaseProvider (line 7) | function SupabaseProvider(props: { children: React.ReactNode }) {
function useSupabaseClient (line 24) | function useSupabaseClient(): SupabaseClient {
FILE: app/lib/urql.tsx
function UrqlProvider (line 5) | function UrqlProvider(props: { children: React.ReactNode }) {
FILE: app/lib/use-paginated-query.ts
function usePaginatedQuery (line 10) | function usePaginatedQuery<Data = any, Variables = object>(
FILE: app/pages/_app.tsx
function MyApp (line 8) | function MyApp({ Component, pageProps }: AppProps) {
FILE: app/pages/account.tsx
function extractExpectedGraphQLErrors (line 82) | function extractExpectedGraphQLErrors(
function AccountForm (line 101) | function AccountForm(props: { profile: DocumentType<typeof ProfileFragme...
FILE: app/pages/comments.tsx
method mergeResult (line 45) | mergeResult(oldData, newData) {
FILE: app/pages/index.tsx
method mergeResult (line 46) | mergeResult(oldData, newData) {
FILE: app/pages/item/[postId].tsx
function PostCommentForm (line 54) | function PostCommentForm(props: { postId: string }) {
FILE: app/pages/newest.tsx
method mergeResult (line 46) | mergeResult(oldData, newData) {
FILE: app/pages/submit.tsx
function extractExpectedGraphQLErrors (line 22) | function extractExpectedGraphQLErrors(
FILE: data/db/backup.sql
function auth (line 215) | CREATE FUNCTION auth.email() RETURNS text
function auth (line 230) | CREATE FUNCTION auth.role() RETURNS text
function auth (line 245) | CREATE FUNCTION auth.uid() RETURNS uuid
function extensions (line 260) | CREATE FUNCTION extensions.grant_pg_cron_access() RETURNS event_trigger
function extensions (line 307) | CREATE FUNCTION extensions.grant_pg_net_access() RETURNS event_trigger
function extensions (line 372) | CREATE FUNCTION extensions.pgrst_ddl_watch() RETURNS event_trigger
function extensions (line 405) | CREATE FUNCTION extensions.pgrst_drop_watch() RETURNS event_trigger
function pgbouncer (line 436) | CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(usernam...
function public (line 453) | CREATE FUNCTION public.graphql("operationName" text DEFAULT NULL::text, ...
function public (line 464) | CREATE FUNCTION public.handle_new_user() RETURNS trigger
function public (line 479) | CREATE FUNCTION public.update_vote_counts() RETURNS trigger
function realtime (line 547) | CREATE FUNCTION realtime.apply_rls(wal jsonb, max_record_bytes integer D...
function realtime (line 821) | CREATE FUNCTION realtime."cast"(val text, type_ regtype) RETURNS jsonb
function realtime (line 936) | CREATE FUNCTION realtime.subscription_check_filters() RETURNS trigger
function storage (line 1007) | CREATE FUNCTION storage.extension(name text) RETURNS text
function storage (line 1026) | CREATE FUNCTION storage.filename(name text) RETURNS text
function storage (line 1042) | CREATE FUNCTION storage.foldername(name text) RETURNS text[]
function storage (line 1058) | CREATE FUNCTION storage.get_size_by_bucket() RETURNS TABLE(size bigint, ...
function storage (line 1074) | CREATE FUNCTION storage.search(prefix text, bucketname text, limits inte...
function supabase_functions (line 1099) | CREATE FUNCTION supabase_functions.http_request() RETURNS trigger
type auth (line 1184) | CREATE TABLE auth.audit_log_entries (
type auth (line 1203) | CREATE TABLE auth.identities (
type auth (line 1225) | CREATE TABLE auth.instances (
type auth (line 1245) | CREATE TABLE auth.refresh_tokens (
type auth (line 1287) | CREATE TABLE auth.schema_migrations (
type auth (line 1303) | CREATE TABLE auth.users (
type public (line 1349) | CREATE TABLE public."Comment" (
type public (line 1384) | CREATE TABLE public."Vote" (
type public (line 1419) | CREATE TABLE public."Post" (
type public (line 1462) | CREATE TABLE public."Profile" (
type public (line 1484) | CREATE TABLE public.schema_migrations (
type realtime (line 1493) | CREATE TABLE realtime.schema_migrations (
type realtime (line 1503) | CREATE TABLE realtime.subscription (
type storage (line 1532) | CREATE TABLE storage.buckets (
type storage (line 1546) | CREATE TABLE storage.migrations (
type storage (line 1558) | CREATE TABLE storage.objects (
type supabase_functions (line 1574) | CREATE TABLE supabase_functions.hooks (
type supabase_functions (line 1613) | CREATE TABLE supabase_functions.migrations (
type audit_logs_instance_id_idx (line 1829) | CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING ...
type identities_user_id_idx (line 1836) | CREATE INDEX identities_user_id_idx ON auth.identities USING btree (user...
type refresh_tokens_instance_id_idx (line 1843) | CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING...
type refresh_tokens_instance_id_user_id_idx (line 1850) | CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_toke...
type refresh_tokens_parent_idx (line 1857) | CREATE INDEX refresh_tokens_parent_idx ON auth.refresh_tokens USING btre...
type refresh_tokens_token_idx (line 1864) | CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree...
type users_instance_id_email_idx (line 1871) | CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (inst...
type users_instance_id_idx (line 1878) | CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id)
type idx_one_vote_per_post (line 1885) | CREATE UNIQUE INDEX idx_one_vote_per_post ON public."Vote" USING btree (...
type idx_unique_post_url (line 1892) | CREATE UNIQUE INDEX idx_unique_post_url ON public."Post" USING btree (url)
type ix_realtime_subscription_entity (line 1899) | CREATE INDEX ix_realtime_subscription_entity ON realtime.subscription US...
type subscription_subscription_id_entity_filters_key (line 1906) | CREATE UNIQUE INDEX subscription_subscription_id_entity_filters_key ON r...
type bname (line 1913) | CREATE UNIQUE INDEX bname ON storage.buckets USING btree (name)
type bucketid_objname (line 1920) | CREATE UNIQUE INDEX bucketid_objname ON storage.objects USING btree (buc...
type name_prefix_search (line 1927) | CREATE INDEX name_prefix_search ON storage.objects USING btree (name tex...
type supabase_functions_hooks_h_table_id_h_name_idx (line 1934) | CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_...
type supabase_functions_hooks_request_id_idx (line 1941) | CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functio...
FILE: data/db/schema.sql
function auth (line 222) | CREATE FUNCTION auth.email() RETURNS text
function auth (line 237) | CREATE FUNCTION auth.role() RETURNS text
function auth (line 252) | CREATE FUNCTION auth.uid() RETURNS uuid
function extensions (line 267) | CREATE FUNCTION extensions.grant_pg_cron_access() RETURNS event_trigger
function extensions (line 314) | CREATE FUNCTION extensions.grant_pg_graphql_access() RETURNS event_trigger
function extensions (line 367) | CREATE FUNCTION extensions.grant_pg_net_access() RETURNS event_trigger
function extensions (line 432) | CREATE FUNCTION extensions.pgrst_ddl_watch() RETURNS event_trigger
function extensions (line 465) | CREATE FUNCTION extensions.pgrst_drop_watch() RETURNS event_trigger
function extensions (line 496) | CREATE FUNCTION extensions.set_graphql_placeholder() RETURNS event_trigger
function pgbouncer (line 554) | CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(usernam...
function public (line 571) | CREATE FUNCTION public.graphql("operationName" text DEFAULT NULL::text, ...
function public (line 582) | CREATE FUNCTION public.handle_new_user() RETURNS trigger
function public (line 597) | CREATE FUNCTION public.update_vote_counts() RETURNS trigger
function realtime (line 665) | CREATE FUNCTION realtime.apply_rls(wal jsonb, max_record_bytes integer D...
function realtime (line 939) | CREATE FUNCTION realtime."cast"(val text, type_ regtype) RETURNS jsonb
function realtime (line 1054) | CREATE FUNCTION realtime.subscription_check_filters() RETURNS trigger
function storage (line 1125) | CREATE FUNCTION storage.extension(name text) RETURNS text
function storage (line 1144) | CREATE FUNCTION storage.filename(name text) RETURNS text
function storage (line 1160) | CREATE FUNCTION storage.foldername(name text) RETURNS text[]
function storage (line 1176) | CREATE FUNCTION storage.get_size_by_bucket() RETURNS TABLE(size bigint, ...
function storage (line 1192) | CREATE FUNCTION storage.search(prefix text, bucketname text, limits inte...
type auth (line 1346) | CREATE TABLE auth.audit_log_entries (
type auth (line 1365) | CREATE TABLE auth.identities (
type auth (line 1387) | CREATE TABLE auth.instances (
type auth (line 1407) | CREATE TABLE auth.refresh_tokens (
type auth (line 1449) | CREATE TABLE auth.schema_migrations (
type auth (line 1465) | CREATE TABLE auth.users (
type public (line 1513) | CREATE TABLE public."Comment" (
type public (line 1548) | CREATE TABLE public."Vote" (
type public (line 1583) | CREATE TABLE public."Post" (
type public (line 1626) | CREATE TABLE public."Profile" (
type public (line 1648) | CREATE TABLE public.schema_migrations (
type realtime (line 1657) | CREATE TABLE realtime.schema_migrations (
type realtime (line 1667) | CREATE TABLE realtime.subscription (
type storage (line 1696) | CREATE TABLE storage.buckets (
type storage (line 1710) | CREATE TABLE storage.migrations (
type storage (line 1722) | CREATE TABLE storage.objects (
type supabase_functions (line 1738) | CREATE TABLE supabase_functions.hooks (
type supabase_functions (line 1777) | CREATE TABLE supabase_functions.migrations (
type audit_logs_instance_id_idx (line 1993) | CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING ...
type identities_user_id_idx (line 2000) | CREATE INDEX identities_user_id_idx ON auth.identities USING btree (user...
type refresh_tokens_instance_id_idx (line 2007) | CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING...
type refresh_tokens_instance_id_user_id_idx (line 2014) | CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_toke...
type refresh_tokens_parent_idx (line 2021) | CREATE INDEX refresh_tokens_parent_idx ON auth.refresh_tokens USING btre...
type refresh_tokens_token_idx (line 2028) | CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree...
type users_instance_id_email_idx (line 2035) | CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (inst...
type users_instance_id_idx (line 2042) | CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id)
type idx_one_vote_per_post (line 2049) | CREATE UNIQUE INDEX idx_one_vote_per_post ON public."Vote" USING btree (...
type idx_unique_post_url (line 2056) | CREATE UNIQUE INDEX idx_unique_post_url ON public."Post" USING btree (url)
type ix_realtime_subscription_entity (line 2063) | CREATE INDEX ix_realtime_subscription_entity ON realtime.subscription US...
type subscription_subscription_id_entity_filters_key (line 2070) | CREATE UNIQUE INDEX subscription_subscription_id_entity_filters_key ON r...
type bname (line 2077) | CREATE UNIQUE INDEX bname ON storage.buckets USING btree (name)
type bucketid_objname (line 2084) | CREATE UNIQUE INDEX bucketid_objname ON storage.objects USING btree (buc...
type name_prefix_search (line 2091) | CREATE INDEX name_prefix_search ON storage.objects USING btree (name tex...
type supabase_functions_hooks_h_table_id_h_name_idx (line 2098) | CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_...
type supabase_functions_hooks_request_id_idx (line 2105) | CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functio...
FILE: data/supabase/01-auth-schema.sql
type auth (line 6) | CREATE TABLE auth.users (
type users_instance_id_email_idx (line 30) | CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (inst...
type users_instance_id_idx (line 31) | CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id)
type auth (line 36) | CREATE TABLE auth.refresh_tokens (
type refresh_tokens_instance_id_idx (line 46) | CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING...
type refresh_tokens_instance_id_user_id_idx (line 47) | CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_toke...
type refresh_tokens_token_idx (line 48) | CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree...
type auth (line 53) | CREATE TABLE auth.instances (
type auth (line 65) | CREATE TABLE auth.audit_log_entries (
type audit_logs_instance_id_idx (line 72) | CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING ...
type auth (line 77) | CREATE TABLE auth.schema_migrations (
function auth (line 92) | create or replace function auth.uid()
function auth (line 103) | create or replace function auth.role()
function auth (line 114) | create or replace function auth.email()
FILE: data/supabase/02-storage-schema.sql
type "storage" (line 8) | CREATE TABLE "storage"."buckets" (
type "storage" (line 17) | CREATE UNIQUE INDEX "bname" ON "storage"."buckets" USING BTREE ("name")
type "storage" (line 19) | CREATE TABLE "storage"."objects" (
type "storage" (line 32) | CREATE UNIQUE INDEX "bucketid_objname" ON "storage"."objects" USING BTRE...
type name_prefix_search (line 33) | CREATE INDEX name_prefix_search ON storage.objects(name text_pattern_ops)
function storage (line 37) | CREATE FUNCTION storage.foldername(name text)
function storage (line 49) | CREATE FUNCTION storage.filename(name text)
function storage (line 61) | CREATE FUNCTION storage.extension(name text)
type storage (line 98) | CREATE TABLE IF NOT EXISTS storage.migrations (
FILE: data/supabase/03-post-setup.sql
function extensions (line 15) | CREATE OR REPLACE FUNCTION extensions.grant_pg_cron_access()
FILE: data/supabase/04-public-profiles.sql
type "Profile" (line 2) | create table "Profile" (
FILE: data/supabase/05-setup-user-profile-trigger.sql
function public (line 1) | CREATE OR REPLACE FUNCTION public.handle_new_user()
FILE: data/supabase/06-update-post-vote-counts.sql
function public (line 1) | CREATE OR REPLACE FUNCTION public.update_vote_counts()
FILE: scripts/fetchGraphQLSchema.js
function fetchGraphQLSchema (line 17) | function fetchGraphQLSchema(url, options) {
Condensed preview — 71 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (314K chars).
[
{
"path": ".editorconfig",
"chars": 166,
"preview": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_n"
},
{
"path": ".github/workflows/ci.yml",
"chars": 500,
"preview": "name: ci\non:\n push:\n pull_request:\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkou"
},
{
"path": ".gitignore",
"chars": 65,
"preview": ".idea\n.DS_Store\n.env\n.env.production\nyarn-error.log\nnode_modules\n"
},
{
"path": ".graphqlrc.yml",
"chars": 246,
"preview": "schema: ./graphql/schema/schema.graphql\ndocuments: ./app/**/*.{graphql,js,ts,jsx,tsx}\nextensions:\n codegen:\n generat"
},
{
"path": ".husky/.gitignore",
"chars": 2,
"preview": "_\n"
},
{
"path": ".husky/pre-commit",
"chars": 58,
"preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
},
{
"path": ".vscode/extensions.json",
"chars": 123,
"preview": "{\n \"recommendations\": [\n \"esbenp.prettier-vscode\",\n \"GraphQL.vscode-graphql\",\n \"bradlc.vscode-tailwindcss\"\n ]"
},
{
"path": ".vscode/settings.json",
"chars": 89,
"preview": "{\n \"editor.formatOnSave\": true,\n \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n}\n"
},
{
"path": "README.md",
"chars": 7706,
"preview": "# Supabase GraphQL Example\n\nA basic HackerNews-like clone where posts can be submitted with url links and then up and do"
},
{
"path": "app/.eslintrc.json",
"chars": 40,
"preview": "{\n \"extends\": \"next/core-web-vitals\"\n}\n"
},
{
"path": "app/.gitignore",
"chars": 431,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "app/README.md",
"chars": 1582,
"preview": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js"
},
{
"path": "app/gql/.gitignore",
"chars": 11,
"preview": "*.ts\n*.tsx\n"
},
{
"path": "app/lib/active-link.tsx",
"chars": 1651,
"preview": "import React from \"react\";\nimport { useRouter } from \"next/router\";\nimport Link from \"next/link\";\n\n/**\n * @source https:"
},
{
"path": "app/lib/comment-item.tsx",
"chars": 3027,
"preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/router\";\n\nimport { useMutation "
},
{
"path": "app/lib/container.tsx",
"chars": 158,
"preview": "import React from \"react\";\n\nexport function Container(props: { children: React.ReactNode }) {\n return <main className=\""
},
{
"path": "app/lib/feed-item.tsx",
"chars": 7328,
"preview": "import { Auth } from \"@supabase/ui\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/router\";\nimport React"
},
{
"path": "app/lib/footer.tsx",
"chars": 3959,
"preview": "import Image from \"next/image\";\nimport Link from \"next/link\";\n\nexport function Footer() {\n const navigation = {\n mai"
},
{
"path": "app/lib/icons.tsx",
"chars": 4009,
"preview": "import React from \"react\";\n\nexport function CalendarIcon(props: { className?: string }) {\n return (\n <svg\n view"
},
{
"path": "app/lib/loading.tsx",
"chars": 276,
"preview": "import React from \"react\";\nimport { LightningBoltIcon } from \"@heroicons/react/solid\";\n\nexport function Loading() {\n re"
},
{
"path": "app/lib/main-section.tsx",
"chars": 243,
"preview": "import React from \"react\";\n\nexport function MainSection(props: { children: React.ReactNode }) {\n return (\n <main cla"
},
{
"path": "app/lib/navigation.tsx",
"chars": 2419,
"preview": "import { Auth } from \"@supabase/ui\";\nimport Link from \"next/link\";\nimport React from \"react\";\nimport { ActiveLink } from"
},
{
"path": "app/lib/noop-uuid.ts",
"chars": 129,
"preview": "/**\n * Noop UUID for GraphQL operations that require an UUID\n */\nexport const noopUUID = \"00000000-0000-0000-0000-000000"
},
{
"path": "app/lib/supabase.tsx",
"chars": 1031,
"preview": "import React from \"react\";\nimport { createClient, SupabaseClient } from \"@supabase/supabase-js\";\nimport { Auth } from \"@"
},
{
"path": "app/lib/time-ago.ts",
"chars": 173,
"preview": "import TimeAgo from \"javascript-time-ago\";\nimport en from \"javascript-time-ago/locale/en.json\";\n\nTimeAgo.addDefaultLocal"
},
{
"path": "app/lib/urql.tsx",
"chars": 927,
"preview": "import React from \"react\";\nimport { createClient, Provider } from \"urql\";\nimport { useSupabaseClient } from \"./supabase\""
},
{
"path": "app/lib/use-paginated-query.ts",
"chars": 1201,
"preview": "import React from \"react\";\nimport { useQuery, UseQueryArgs, UseQueryResponse } from \"urql\";\n\n/**\n * Urql only supports \""
},
{
"path": "app/next-env.d.ts",
"chars": 201,
"preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edite"
},
{
"path": "app/next.config.js",
"chars": 120,
"preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n reactStrictMode: true,\n};\n\nmodule.exports = nextConfig;\n"
},
{
"path": "app/package.json",
"chars": 812,
"preview": "{\n \"name\": \"app\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\",\n \"build\": \"next bui"
},
{
"path": "app/pages/_app.tsx",
"chars": 522,
"preview": "import \"../styles/globals.css\";\nimport type { AppProps } from \"next/app\";\nimport { UrqlProvider } from \"../lib/urql\";\nim"
},
{
"path": "app/pages/about.tsx",
"chars": 8638,
"preview": "import React from \"react\";\nimport type { NextPage } from \"next\";\nimport Head from \"next/head\";\nimport Link from \"next/li"
},
{
"path": "app/pages/account.tsx",
"chars": 4715,
"preview": "import React from \"react\";\nimport { NextPage } from \"next\";\nimport { useRouter } from \"next/router\";\nimport { Auth, Butt"
},
{
"path": "app/pages/api/graphiql.ts",
"chars": 542,
"preview": "import { renderGraphiQL } from \"@graphql-yoga/render-graphiql\";\nimport { NextApiRequest, NextApiResponse } from \"next\";\n"
},
{
"path": "app/pages/comments.tsx",
"chars": 2671,
"preview": "import { Button } from \"@supabase/ui\";\nimport type { NextPage } from \"next\";\nimport Head from \"next/head\";\nimport Image "
},
{
"path": "app/pages/index.tsx",
"chars": 2602,
"preview": "import { Auth, Button } from \"@supabase/ui\";\nimport type { NextPage } from \"next\";\nimport Head from \"next/head\";\nimport "
},
{
"path": "app/pages/item/[postId].tsx",
"chars": 3851,
"preview": "import { Auth, Button } from \"@supabase/ui\";\nimport React from \"react\";\nimport type { NextPage } from \"next\";\nimport Hea"
},
{
"path": "app/pages/login.tsx",
"chars": 1131,
"preview": "import React from \"react\";\nimport type { NextPage } from \"next\";\nimport Head from \"next/head\";\nimport { Auth } from \"@su"
},
{
"path": "app/pages/logout.tsx",
"chars": 452,
"preview": "import React from \"react\";\nimport type { NextPage } from \"next\";\nimport { Auth } from \"@supabase/ui\";\nimport { useSupaba"
},
{
"path": "app/pages/newest.tsx",
"chars": 2616,
"preview": "import { Auth, Button } from \"@supabase/ui\";\nimport type { NextPage } from \"next\";\nimport Head from \"next/head\";\nimport "
},
{
"path": "app/pages/profile/[profileId].tsx",
"chars": 4015,
"preview": "import type { NextPage } from \"next\";\nimport Head from \"next/head\";\nimport { useRouter } from \"next/router\";\nimport { us"
},
{
"path": "app/pages/submit.tsx",
"chars": 3079,
"preview": "import React from \"react\";\nimport { NextPage } from \"next\";\nimport Head from \"next/head\";\nimport { useRouter } from \"nex"
},
{
"path": "app/postcss.config.js",
"chars": 83,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "app/styles/globals.css",
"chars": 59,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
},
{
"path": "app/tailwind.config.js",
"chars": 144,
"preview": "module.exports = {\n content: [\"./pages/**/*.{js,ts,jsx,tsx}\", \"./lib/**/*.{js,ts,jsx,tsx}\"],\n theme: {\n extend: {},"
},
{
"path": "app/tsconfig.json",
"chars": 509,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"sk"
},
{
"path": "data/db/backup.sql",
"chars": 60781,
"preview": "SET statement_timeout = 0;\nSET lock_timeout = 0;\nSET idle_in_transaction_session_timeout = 0;\nSET client_encoding = 'UTF"
},
{
"path": "data/db/row_level_security_polices.csv",
"chars": 1957,
"preview": "schemaname,tablename,policyname,permissive,roles,cmd,qual,with_check\npublic,Profile,Public profiles are viewable by ever"
},
{
"path": "data/db/schema.sql",
"chars": 66453,
"preview": "SET statement_timeout = 0;\nSET lock_timeout = 0;\nSET idle_in_transaction_session_timeout = 0;\nSET client_encoding = 'UTF"
},
{
"path": "data/seed/blog.xml",
"chars": 30560,
"preview": "\n <rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n <channel>\n <title>Blog - Supabase</title>\n "
},
{
"path": "data/seed/blog_posts.csv",
"chars": 11515,
"preview": "\"profileId\",\"title\",\"url\"\n\"5fdfeaaa-c9aa-40fe-8d8a-d0f3abbf4ceb\",\"Blog - Supabase\",\"https://supabase.com\"\n\"5fdfeaaa-c9aa"
},
{
"path": "data/seed/comments.csv",
"chars": 4678,
"preview": "postId,profileId,message\n32,5fdfeaaa-c9aa-40fe-8d8a-d0f3abbf4ceb,I agree!\n33,3e223118-04b2-4faa-8ed9-d3995fc50975,Great "
},
{
"path": "data/supabase/00-initial-schema.sql",
"chars": 2487,
"preview": "-- Set up realtime\ncreate schema if not exists realtime;\n-- create publication supabase_realtime; -- defaults to empty p"
},
{
"path": "data/supabase/01-auth-schema.sql",
"chars": 4838,
"preview": "\nCREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION supabase_admin;\n\n-- auth.users definition\n\nCREATE TABLE auth.users (\n\tin"
},
{
"path": "data/supabase/02-storage-schema.sql",
"chars": 4311,
"preview": "CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION supabase_admin;\n\ngrant usage on schema storage to postgres, anon, auth"
},
{
"path": "data/supabase/03-post-setup.sql",
"chars": 2828,
"preview": "ALTER ROLE postgres SET search_path TO \"\\$user\",public,extensions;\nCREATE OR REPLACE FUNCTION extensions.notify_api_rest"
},
{
"path": "data/supabase/04-public-profiles.sql",
"chars": 1306,
"preview": "-- Create a table for Public Profiles\ncreate table \"Profile\" (\n id uuid references auth.users not null,\n updatedAt tim"
},
{
"path": "data/supabase/05-setup-total-counts.sql",
"chars": 303,
"preview": "comment on table \"Profile\" is '@graphql({\"totalCount\": {\"enabled\": true}})';\ncomment on table \"Post\" is '@graphql({\"tota"
},
{
"path": "data/supabase/05-setup-user-profile-trigger.sql",
"chars": 483,
"preview": "CREATE OR REPLACE FUNCTION public.handle_new_user()\n RETURNS trigger\n LANGUAGE plpgsql\n SECURITY DEFINER\nAS $function$\nb"
},
{
"path": "data/supabase/06-update-post-vote-counts.sql",
"chars": 1639,
"preview": "CREATE OR REPLACE FUNCTION public.update_vote_counts()\nRETURNS trigger as $$\nBEGIN\n\nWITH r AS (\nSELECT\n\tcoalesce(\"Vote\"."
},
{
"path": "data/supabase/07-add-post-title-url-constraints.sql",
"chars": 165,
"preview": "ALTER TABLE \"Post\"\nADD CONSTRAINT post_title_length check (char_length(title) > 0);\n\nALTER TABLE \"Post\"\nADD CONSTRAINT p"
},
{
"path": "data/supabase/08-update-post-cascahe-delete-constraints.sql",
"chars": 442,
"preview": "ALTER TABLE ONLY public.\"Comment\"\n DROP CONSTRAINT \"Comment_postId_fkey\";\n\nALTER TABLE ONLY public.\"Comment\"\n ADD "
},
{
"path": "data/supabase/09-update-all-gravatars.sql",
"chars": 236,
"preview": "WITH a AS (\n\tSELECT\n\t\tid,\n\t\t'https://www.gravatar.com/avatar/' || md5(email) || '?d=mp' AS \"avatarUrl\",\n\t\temail\n\tFROM\n\t\t"
},
{
"path": "data/supabase/rls-policies.md",
"chars": 4688,
"preview": "# Row Level Security Matrix (RLS)\n\n## Profile\n\n- All users can `SELECT` all `PROFILE`s\n- Only authenticated users can CR"
},
{
"path": "graphql/queries/feed.graphql",
"chars": 667,
"preview": "# Your GraphQL query or mutation goes here\nquery {\n feed: postCollection {\n edges {\n post: node {\n id\n "
},
{
"path": "graphql/queries/hasProfileVotes.graphql",
"chars": 587,
"preview": "query {\n post: postCollection(filter: { id: { eq: 22 } }, first: 1) {\n edges {\n post: node {\n id\n "
},
{
"path": "graphql/queries/rankedFeed.graphql",
"chars": 659,
"preview": "query {\n rankedFeed: postCollection(\n orderBy: [\n { voteRank: AscNullsFirst }\n { score: DescNullsFirst }\n "
},
{
"path": "graphql/schema/schema.graphql",
"chars": 17409,
"preview": "scalar BigInt\n\n\"\"\"\nBoolean expression comparing fields on type \"BigInt\"\n\"\"\"\ninput BigIntFilter {\n eq: BigInt\n gt: BigI"
},
{
"path": "package.json",
"chars": 726,
"preview": "{\n \"name\": \"pg-graphql-example\",\n \"workspaces\": [\n \"app\"\n ],\n \"private\": true,\n \"devDependencies\": {\n \"@graph"
},
{
"path": "renovate.json",
"chars": 131,
"preview": "{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\"github>the-guild-org/shared-config:re"
},
{
"path": "scripts/fetchGraphQLSchema.js",
"chars": 1687,
"preview": "require(\"dotenv\").config();\n\nconst fs = require(\"fs\");\nconst gradient = require(\"gradient-string\");\nconst path = require"
}
]
About this extraction
This page contains the full source code of the supabase-community/supabase-graphql-example GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 71 files (288.9 KB), approximately 80.2k tokens, and a symbol index with 168 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.