mirror of
https://github.com/jordienr/zenblog.git
synced 2026-09-27 17:45:55 -04:00
wip
This commit is contained in:
@@ -31,7 +31,7 @@ const layout = async ({
|
||||
<Link
|
||||
data-state={params.slug === post.slug ? "active" : "inactive"}
|
||||
className={
|
||||
"group flex items-center gap-1 rounded-md p-1.5 text-slate-600 transition-all hover:text-orange-600 data-[state=active]:bg-orange-50 data-[state=active]:text-orange-600"
|
||||
"group flex items-center gap-1 rounded-md p-1.5 text-zinc-600 transition-all hover:text-orange-600 data-[state=active]:bg-orange-50 data-[state=active]:text-orange-600"
|
||||
}
|
||||
key={post.slug}
|
||||
href={"/docs/" + post.slug}
|
||||
@@ -44,7 +44,7 @@ const layout = async ({
|
||||
</Link>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-auto justify-self-end p-4 text-center text-xs text-slate-500">
|
||||
<div className="mt-auto justify-self-end p-4 text-center text-xs text-zinc-500">
|
||||
<p>Docs site built with zenblog</p>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -68,7 +68,7 @@ const layout = async ({
|
||||
</Button>
|
||||
</div>
|
||||
<div className="p-2">
|
||||
{/* <h2 className="text-sm font-medium text-slate-500">On this page</h2> */}
|
||||
{/* <h2 className="text-sm font-medium text-zinc-500">On this page</h2> */}
|
||||
<ul className="mt-2">
|
||||
{/* <li># title 1</li>
|
||||
<li># title 2</li>
|
||||
|
||||
@@ -106,6 +106,11 @@ const EditorSettings = (props: Props) => {
|
||||
Add metadata
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Categories</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button>Save</Button>
|
||||
</div>
|
||||
|
||||
@@ -260,7 +260,11 @@ export const ZendoEditor = (props: Props) => {
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent>
|
||||
<EditorSettings onSave={() => {}}></EditorSettings>
|
||||
<EditorSettings
|
||||
onSave={() => {
|
||||
// TO DO - save settings
|
||||
}}
|
||||
></EditorSettings>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<Button type="submit">
|
||||
@@ -333,9 +337,6 @@ export const ZendoEditor = (props: Props) => {
|
||||
>
|
||||
<EditorContent className="" editor={editor} />
|
||||
</div>
|
||||
<Debugger>
|
||||
<pre>{JSON.stringify(editor?.getJSON(), null, 2)}</pre>
|
||||
</Debugger>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface TextareaProps
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"border-input bg-background ring-offset-background placeholder:text-muted-foreground flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea };
|
||||
@@ -2,13 +2,24 @@ import UserButton from "@/components/UserButton";
|
||||
import ZendoLogo from "@/components/ZendoLogo";
|
||||
import { Github, Twitter } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useSupabaseClient } from "@supabase/auth-helpers-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
loading?: boolean;
|
||||
};
|
||||
export default function AppLayout({ children, loading }: Props) {
|
||||
// const { isSignedIn } = useAuth();
|
||||
const sb = useSupabaseClient();
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -35,9 +46,61 @@ export default function AppLayout({ children, loading }: Props) {
|
||||
Blogs
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex gap-1">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="rounded-full px-3 py-1 text-sm font-medium text-slate-600 hover:bg-orange-50 hover:text-orange-600">
|
||||
Feedback
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const form = e.target as HTMLFormElement;
|
||||
const formData = new FormData(
|
||||
e.target as HTMLFormElement
|
||||
);
|
||||
const feedback = formData.get("feedback") as string;
|
||||
|
||||
const user = await sb.auth.getUser();
|
||||
|
||||
if (user.error) {
|
||||
throw user.error;
|
||||
}
|
||||
|
||||
const { data, error } = await sb
|
||||
.from("feedback")
|
||||
.insert({ feedback, user_email: user.data.user.email });
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
toast.success("Thanks for the feedback!");
|
||||
form.reset();
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
"Failed to submit feedback. Check the console for more details."
|
||||
);
|
||||
console.error("Failed to submit feedback", error);
|
||||
}
|
||||
}}
|
||||
className="grid gap-2"
|
||||
>
|
||||
<Label htmlFor="feedback">
|
||||
<div className="sr-only">Feedback</div>
|
||||
<Textarea
|
||||
placeholder="Your feedback"
|
||||
name="feedback"
|
||||
id="feedback"
|
||||
/>
|
||||
</Label>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Link
|
||||
className="rounded-full px-3 py-1 text-sm font-medium text-slate-600 hover:bg-orange-50 hover:text-orange-600"
|
||||
className="mr-2 rounded-full px-3 py-1 text-sm font-medium text-slate-600 hover:bg-orange-50 hover:text-orange-600"
|
||||
href="/docs/getting-started"
|
||||
>
|
||||
Docs
|
||||
@@ -46,7 +109,14 @@ export default function AppLayout({ children, loading }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="min-h-screen bg-slate-50 pb-24 shadow-md">{children}</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="min-h-screen bg-slate-50 pb-24 shadow-md"
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
<footer className="flex justify-between border-t bg-slate-100 p-8 font-mono text-slate-700">
|
||||
<ul>
|
||||
<li>thanks for checking out zenblog</li>
|
||||
|
||||
@@ -49,25 +49,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Hydrate state={pageProps.dehydratedState}>
|
||||
<>
|
||||
<motion.div
|
||||
key={pathname}
|
||||
initial="pageInitial"
|
||||
animate="pageAnimate"
|
||||
exit={{ opacity: 0 }}
|
||||
variants={{
|
||||
pageInitial: {
|
||||
opacity: 0,
|
||||
y: -10,
|
||||
},
|
||||
pageAnimate: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Component key={pathname} {...pageProps} />
|
||||
</motion.div>
|
||||
|
||||
<Component key={pathname} {...pageProps} />
|
||||
<Toaster />
|
||||
</>
|
||||
</Hydrate>
|
||||
|
||||
@@ -179,7 +179,7 @@ const Home = () => {
|
||||
Star us on GitHub
|
||||
</Link>
|
||||
</div>
|
||||
<h1 className="mt-2 bg-gradient-to-b from-zinc-800 via-zinc-500 to-zinc-800 bg-clip-text text-3xl tracking-tight text-transparent md:text-5xl">
|
||||
<h1 className="mt-2 bg-gradient-to-b from-zinc-800 via-zinc-500 to-zinc-800 bg-clip-text text-3xl font-medium tracking-tight text-transparent md:text-5xl">
|
||||
Add a blog to your <br /> website{" "}
|
||||
<span className="bg-gradient-to-br from-orange-400 to-orange-600 bg-clip-text text-transparent">
|
||||
in 2 minutes
|
||||
|
||||
Vendored
+6
-2
@@ -16,8 +16,12 @@ export type CreateClientOpts = {
|
||||
};
|
||||
export declare function createClient({ blogId, _url, debug }: CreateClientOpts): {
|
||||
posts: {
|
||||
getAll: () => Promise<Post[]>;
|
||||
getBySlug: (slug: string) => Promise<PostWithContent>;
|
||||
getAll: (opts?: {
|
||||
cache?: RequestInit["cache"];
|
||||
} | undefined) => Promise<Post[]>;
|
||||
getBySlug: (slug: string, opts?: {
|
||||
cache?: RequestInit["cache"];
|
||||
} | undefined) => Promise<PostWithContent>;
|
||||
};
|
||||
};
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,gBAAgB;;sBA8BrC,QAAQ,IAAI,EAAE,CAAC;0BAqBT,MAAM,KAAG,QAAQ,eAAe,CAAC;;EAcvE"}
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,gBAAgB;;;oBA6B1D,WAAW,CAAC,OAAO,CAAC;0BAKe,QAAQ,IAAI,EAAE,CAAC;0BAuBhD,MAAM;oBA5BR,WAAW,CAAC,OAAO,CAAC;0BA8BvB,QAAQ,eAAe,CAAC;;EAehC"}
|
||||
Vendored
+4
-2
@@ -50,12 +50,13 @@ function createClient({ blogId, _url, debug }) {
|
||||
}
|
||||
return {
|
||||
posts: {
|
||||
getAll: async function () {
|
||||
getAll: async function (opts) {
|
||||
const posts = await _fetch(`posts`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
cache: opts?.cache || "default",
|
||||
});
|
||||
log("posts.getAll", posts);
|
||||
const normalizedPosts = posts.map((post) => {
|
||||
@@ -69,12 +70,13 @@ function createClient({ blogId, _url, debug }) {
|
||||
});
|
||||
return normalizedPosts; // to do: validate
|
||||
},
|
||||
getBySlug: async function (slug) {
|
||||
getBySlug: async function (slug, opts) {
|
||||
const post = await _fetch(`post/${slug}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
cache: opts?.cache || "default",
|
||||
});
|
||||
log("posts.getBySlug", post);
|
||||
return post; // to do: validate
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
||||
@@ -73,14 +73,19 @@ export function createClient({ blogId, _url, debug }: CreateClientOpts) {
|
||||
throwError("blogId is required");
|
||||
}
|
||||
|
||||
type ReqOpts = {
|
||||
cache?: RequestInit["cache"];
|
||||
};
|
||||
|
||||
return {
|
||||
posts: {
|
||||
getAll: async function (): Promise<Post[]> {
|
||||
getAll: async function (opts?: ReqOpts): Promise<Post[]> {
|
||||
const posts = await _fetch(`posts`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
cache: opts?.cache || "default",
|
||||
});
|
||||
log("posts.getAll", posts);
|
||||
|
||||
@@ -96,12 +101,16 @@ export function createClient({ blogId, _url, debug }: CreateClientOpts) {
|
||||
|
||||
return normalizedPosts as Post[]; // to do: validate
|
||||
},
|
||||
getBySlug: async function (slug: string): Promise<PostWithContent> {
|
||||
getBySlug: async function (
|
||||
slug: string,
|
||||
opts?: ReqOpts
|
||||
): Promise<PostWithContent> {
|
||||
const post = await _fetch(`post/${slug}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
cache: opts?.cache || "default",
|
||||
});
|
||||
|
||||
log("posts.getBySlug", post);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Supabase
|
||||
.branches
|
||||
.temp
|
||||
.env
|
||||
|
||||
+82
-15
@@ -1,8 +1,9 @@
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the working
|
||||
# directory name when running `supabase init`.
|
||||
project_id = "zendo"
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the
|
||||
# working directory name when running `supabase init`.
|
||||
project_id = "zenblog"
|
||||
|
||||
[api]
|
||||
enabled = true
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
@@ -17,35 +18,65 @@ max_rows = 1000
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
# Port used by db diff command to initialize the shadow database.
|
||||
shadow_port = 54320
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 15
|
||||
|
||||
[db.pooler]
|
||||
enabled = false
|
||||
# Port to use for the local connection pooler.
|
||||
port = 54329
|
||||
# Specifies when a server connection can be reused by other clients.
|
||||
# Configure one of the supported pooler modes: `transaction`, `session`.
|
||||
pool_mode = "transaction"
|
||||
# How many server connections to allow per user/database pair.
|
||||
default_pool_size = 20
|
||||
# Maximum number of client connections allowed.
|
||||
max_client_conn = 100
|
||||
|
||||
[realtime]
|
||||
enabled = true
|
||||
# Bind realtime via either IPv4 or IPv6. (default: IPv6)
|
||||
# ip_version = "IPv6"
|
||||
|
||||
[studio]
|
||||
enabled = true
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
# External URL of the API server that frontend connects to.
|
||||
api_url = "http://127.0.0.1"
|
||||
|
||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||
[inbucket]
|
||||
enabled = true
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
smtp_port = 54325
|
||||
pop3_port = 54326
|
||||
# Uncomment to expose additional ports for testing user applications that send emails.
|
||||
# smtp_port = 54325
|
||||
# pop3_port = 54326
|
||||
|
||||
[storage]
|
||||
enabled = true
|
||||
# The maximum file size allowed (e.g. "5MB", "500KB").
|
||||
file_size_limit = "50MiB"
|
||||
|
||||
[auth]
|
||||
enabled = true
|
||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
||||
# in emails.
|
||||
site_url = "http://localhost:3000"
|
||||
site_url = "http://127.0.0.1:3000"
|
||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
additional_redirect_urls = ["https://localhost:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
|
||||
# week).
|
||||
additional_redirect_urls = ["https://127.0.0.1:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
||||
jwt_expiry = 3600
|
||||
# If disabled, the refresh token will never expire.
|
||||
enable_refresh_token_rotation = true
|
||||
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
|
||||
# Requires enable_refresh_token_rotation = true.
|
||||
refresh_token_reuse_interval = 10
|
||||
# Allow/disallow new user signups to your project.
|
||||
enable_signup = true
|
||||
|
||||
@@ -58,13 +89,39 @@ double_confirm_changes = true
|
||||
# If enabled, users need to confirm their email address before signing in.
|
||||
enable_confirmations = false
|
||||
|
||||
# Uncomment to customize email template
|
||||
# [auth.email.template.invite]
|
||||
# subject = "You have been invited"
|
||||
# content_path = "./supabase/templates/invite.html"
|
||||
|
||||
[auth.sms]
|
||||
# Allow/disallow new user signups via SMS to your project.
|
||||
enable_signup = true
|
||||
# If enabled, users need to confirm their phone number before signing in.
|
||||
enable_confirmations = false
|
||||
# Template for sending OTP to users
|
||||
template = "Your code is {{ .Code }} ."
|
||||
|
||||
# Use pre-defined map of phone number to OTP for testing.
|
||||
[auth.sms.test_otp]
|
||||
# 4152127777 = "123456"
|
||||
|
||||
# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
|
||||
[auth.sms.twilio]
|
||||
enabled = false
|
||||
account_sid = ""
|
||||
message_service_sid = ""
|
||||
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
|
||||
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
|
||||
|
||||
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
|
||||
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`,
|
||||
# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
|
||||
[auth.external.apple]
|
||||
enabled = false
|
||||
client_id = ""
|
||||
secret = ""
|
||||
# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
|
||||
secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
|
||||
# Overrides the default auth redirectUrl.
|
||||
redirect_uri = ""
|
||||
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
|
||||
@@ -75,8 +132,18 @@ url = ""
|
||||
enabled = false
|
||||
port = 54327
|
||||
vector_port = 54328
|
||||
# Setup BigQuery project to enable log viewer on local development stack.
|
||||
# See: https://supabase.com/docs/guides/getting-started/local-development#enabling-local-logging
|
||||
gcp_project_id = ""
|
||||
gcp_project_number = ""
|
||||
gcp_jwt_path = "supabase/gcloud.json"
|
||||
# Configure one of the supported backends: `postgres`, `bigquery`.
|
||||
backend = "postgres"
|
||||
|
||||
# Experimental features may be deprecated any time
|
||||
[experimental]
|
||||
# Configures Postgres storage engine to use OrioleDB (S3)
|
||||
orioledb_version = ""
|
||||
# Configures S3 bucket URL, eg. <bucket>.s3-accelerate.amazonaws.com
|
||||
s3_host = "env(S3_HOST)"
|
||||
# Configures S3 bucket region, eg. us-east-1
|
||||
s3_region = "env(S3_REGION)"
|
||||
# Configures AWS_ACCESS_KEY_ID for S3 bucket
|
||||
s3_access_key = "env(S3_ACCESS_KEY)"
|
||||
# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
|
||||
s3_secret_key = "env(S3_SECRET_KEY)"
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"recommendations": ["denoland.vscode-deno"]
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.lint": true,
|
||||
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
@@ -9,6 +10,8 @@ SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "timescaledb" WITH SCHEMA "extensions";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgsodium" WITH SCHEMA "pgsodium";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
|
||||
@@ -23,7 +26,7 @@ CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault";
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions";
|
||||
|
||||
CREATE FUNCTION "public"."generate_slug"("title" "text") RETURNS "text"
|
||||
CREATE OR REPLACE FUNCTION "public"."generate_slug"("title" "text") RETURNS "text"
|
||||
LANGUAGE "plpgsql"
|
||||
AS $$
|
||||
DECLARE
|
||||
@@ -47,7 +50,7 @@ $$;
|
||||
|
||||
ALTER FUNCTION "public"."generate_slug"("title" "text") OWNER TO "postgres";
|
||||
|
||||
CREATE FUNCTION "public"."requesting_user_id"() RETURNS "text"
|
||||
CREATE OR REPLACE FUNCTION "public"."requesting_user_id"() RETURNS "text"
|
||||
LANGUAGE "sql" STABLE
|
||||
AS $$
|
||||
select nullif(current_setting('request.jwt.claims', true)::json->>'sub', '')::text;
|
||||
@@ -59,7 +62,7 @@ SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = "heap";
|
||||
|
||||
CREATE TABLE "public"."blogs" (
|
||||
CREATE TABLE IF NOT EXISTS "public"."blogs" (
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
|
||||
"title" "text" NOT NULL,
|
||||
@@ -71,7 +74,7 @@ CREATE TABLE "public"."blogs" (
|
||||
|
||||
ALTER TABLE "public"."blogs" OWNER TO "postgres";
|
||||
|
||||
CREATE TABLE "public"."homepage_signup" (
|
||||
CREATE TABLE IF NOT EXISTS "public"."homepage_signup" (
|
||||
"id" bigint NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT "now"(),
|
||||
"name" "text" NOT NULL,
|
||||
@@ -89,7 +92,7 @@ ALTER TABLE "public"."homepage_signup" ALTER COLUMN "id" ADD GENERATED BY DEFAUL
|
||||
CACHE 1
|
||||
);
|
||||
|
||||
CREATE TABLE "public"."invitations" (
|
||||
CREATE TABLE IF NOT EXISTS "public"."invitations" (
|
||||
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
|
||||
"blog_id" "uuid" NOT NULL,
|
||||
"email" "text" NOT NULL,
|
||||
@@ -99,7 +102,7 @@ CREATE TABLE "public"."invitations" (
|
||||
|
||||
ALTER TABLE "public"."invitations" OWNER TO "postgres";
|
||||
|
||||
CREATE TABLE "public"."members" (
|
||||
CREATE TABLE IF NOT EXISTS "public"."members" (
|
||||
"id" bigint NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
|
||||
"user_id" "text" NOT NULL,
|
||||
@@ -117,7 +120,7 @@ ALTER TABLE "public"."members" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDE
|
||||
CACHE 1
|
||||
);
|
||||
|
||||
CREATE TABLE "public"."posts" (
|
||||
CREATE TABLE IF NOT EXISTS "public"."posts" (
|
||||
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
|
||||
"user_id" "text" DEFAULT "public"."requesting_user_id"() NOT NULL,
|
||||
"blog_id" "uuid" NOT NULL,
|
||||
@@ -127,7 +130,8 @@ CREATE TABLE "public"."posts" (
|
||||
"updated_at" timestamp with time zone DEFAULT "now"() NOT NULL,
|
||||
"slug" "text" NOT NULL,
|
||||
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
|
||||
"cover_image" "text"
|
||||
"cover_image" "text",
|
||||
"metadata" "jsonb"[]
|
||||
);
|
||||
|
||||
ALTER TABLE "public"."posts" OWNER TO "postgres";
|
||||
@@ -150,6 +154,10 @@ ALTER TABLE ONLY "public"."posts"
|
||||
ALTER TABLE ONLY "public"."posts"
|
||||
ADD CONSTRAINT "unique_slug_per_user_post_constraint" UNIQUE ("slug", "user_id", "blog_id");
|
||||
|
||||
CREATE INDEX "posts_slug_blog_id_idx" ON "public"."posts" USING "btree" ("slug", "blog_id");
|
||||
|
||||
CREATE INDEX "posts_slug_idx" ON "public"."posts" USING "btree" ("slug");
|
||||
|
||||
ALTER TABLE ONLY "public"."invitations"
|
||||
ADD CONSTRAINT "invitations_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs"("id");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# To Do
|
||||
|
||||
## Features
|
||||
## Ideas
|
||||
|
||||
- [] Add PolyScale cache to Supabase for faster queries?
|
||||
- [] Categories
|
||||
@@ -18,15 +18,15 @@
|
||||
- [x] View all blogs
|
||||
- Make description optional
|
||||
- Add emoji picker to Create Blog
|
||||
- Redirect to blog after creating it
|
||||
- [x] Redirect to blog after creating it
|
||||
|
||||
## Posts
|
||||
|
||||
- [] See "published at" and "updated at" dates in post list
|
||||
- [x] View all posts
|
||||
- [x] Create post
|
||||
- [] View post
|
||||
- [] Edit post
|
||||
- [x] View post
|
||||
- [x] Edit post
|
||||
- [] Delete post
|
||||
|
||||
## SEO
|
||||
@@ -57,12 +57,10 @@
|
||||
- [] As a member, edit a post in a blog
|
||||
- [] As a member, delete a post in a blog
|
||||
|
||||
## React Components
|
||||
## Docs
|
||||
|
||||
- [] Create ZendoProvider component
|
||||
- [] Create PostList component
|
||||
- [] Create Post component
|
||||
- [] Create ContentRenderer component
|
||||
- [] Add documentation for API client
|
||||
- [] Copypastable Zenblog Renderer component
|
||||
|
||||
## User Feedback
|
||||
|
||||
|
||||
Reference in New Issue
Block a user