Skip to content

Commit

Permalink
simplify example
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Calder committed Jun 6, 2023
1 parent a154f3a commit cc4be55
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 190 deletions.
2 changes: 1 addition & 1 deletion examples/nextjs-keystone-supabase-auth/.env.sample
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DATABASE_URL=<SUPABASE_DATABASE_URL>
SUPABASE_DATABASE_URL=<SUPABASE_DATABASE_URL>
NEXT_PUBLIC_SUPABASE_URL=<SUPABASE_URL>
SUPABASE_SERVICE_KEY=<SUPABASE_SERVICE_KEY>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<SUPABASE_ANON_KEY>
26 changes: 1 addition & 25 deletions examples/nextjs-keystone-supabase-auth/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ export function Header({ user }: { user: { name: string } | null }) {
const { supabase } = useSupabase();
const emailRef = useRef<HTMLInputElement | null>(null);
const passwordRef = useRef<HTMLInputElement | null>(null);
const regEmailRef = useRef<HTMLInputElement | null>(null);
const regPasswordRef = useRef<HTMLInputElement | null>(null);

// We are using the supabase client to manage users
const login = async () => {
if (emailRef.current && passwordRef.current) {
const email = emailRef.current.value;
Expand All @@ -28,29 +27,6 @@ export function Header({ user }: { user: { name: string } | null }) {
}
}
};
// register not added to UI, but here for reference.
// A proper registration flow would be to send an email to the user with a link to confirm their email address
// and add the user to keystone.
// This is not implemented in this example.
// See https://supabase.io/docs/guides/auth
// @ts-ignore
const register = async () => {
if (regEmailRef.current && regPasswordRef.current) {
const email = regEmailRef.current.value;
const password = regPasswordRef.current.value;

const { data, error } = await supabase.auth.signUp({
email,
password,
});
if (error) {
console.log('error', error);
}
if (data?.user?.email) {
router.refresh();
}
}
};

const logout = async () => {
console.log('logout');
Expand Down
3 changes: 2 additions & 1 deletion examples/nextjs-keystone-supabase-auth/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ dotenv.config();
export default config<TypeInfo>({
db: {
provider: 'postgresql',
url: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/postgres',
url:
process.env.SUPABASE_DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/postgres',

// WARNING: this is only needed for our monorepo examples, dont do this
prismaClientPath: 'node_modules/.myprisma/client',
Expand Down
31 changes: 11 additions & 20 deletions examples/nextjs-keystone-supabase-auth/keystone/context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { getContext } from '@keystone-6/core/context';
import {
createServerComponentSupabaseClient,
createServerSupabaseClient,
} from '@supabase/auth-helpers-nextjs';
import { NextApiRequest, NextApiResponse } from 'next';
import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs';
import config from '../keystone';
import { Context } from '.keystone/types';
import * as PrismaModule from '.myprisma/client';
Expand All @@ -14,19 +10,14 @@ export const keystoneContext: Context =

if (process.env.NODE_ENV !== 'production') (globalThis as any).keystoneContext = keystoneContext;

export async function getKeystoneSessionContext(props?: {
req: NextApiRequest;
res: NextApiResponse;
}) {
if (props) {
const { data: rawSession } = await createServerSupabaseClient(props).auth.getSession();
return keystoneContext.withSession(rawSession.session?.user.app_metadata?.keystone);
} else {
const { headers, cookies } = require('next/headers');
const { data: rawSession } = await createServerComponentSupabaseClient({
headers,
cookies,
}).auth.getSession();
return keystoneContext.withSession(rawSession.session?.user.app_metadata?.keystone);
}
export async function getKeystoneSessionContext() {
// This is how you would do session context in pages directory
//const { data: rawSession } = await createServerSupabaseClient({req,res}).auth.getSession();
//return keystoneContext.withSession(rawSession.session?.user);
const { headers, cookies } = require('next/headers');
const { data: rawSession } = await createServerComponentSupabaseClient({
headers,
cookies,
}).auth.getSession();
return keystoneContext.withSession(rawSession.session?.user);
}
79 changes: 7 additions & 72 deletions examples/nextjs-keystone-supabase-auth/keystone/schema.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,26 @@
import { list } from '@keystone-6/core';
import { createClient } from '@supabase/supabase-js';
import { allowAll, denyAll, allOperations } from '@keystone-6/core/access';
import { select, text, timestamp } from '@keystone-6/core/fields';
import { allowAll, allOperations } from '@keystone-6/core/access';
import { text, timestamp } from '@keystone-6/core/fields';
import type { Lists } from '.keystone/types';

const permissions = {
authenticatedUser: ({ session }: any) => !!session?.id,
public: () => true,
authenticatedUser: ({ session }: any) => !!session,
};

export const lists: Lists = {
User: list({
Post: list({
// readonly for demo purpose
access: {
operation: {
// deny create/read/update/delete
...allOperations(denyAll),
// Only Supabase Users can create, update, and delete
...allOperations(permissions.authenticatedUser),
// override the deny and allow only query
query: allowAll,
},
},
hooks: {
async beforeOperation({ operation, item, resolvedData }) {
// On Create of Update, update the users Supabase app_metadata this is what shows up in the users session
// doing this in a beforeOperation hook means that the user will be updated in supabase before the keystone user is updated
// if there is an error updating the supabase user, the keystone user will not be updated
if (operation === 'create' || operation === 'update') {
const supabaseSudoClient = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!
);
try {
const subjectId = item?.subjectId || resolvedData?.subjectId;
if (!subjectId) {
throw new Error('subjectId is required');
}
await supabaseSudoClient.auth.admin.updateUserById(subjectId as string, {
app_metadata: { keystone: item },
});
} catch (error) {
// throw an error if the user failed to update in supabase
// this will prevent the keystone user from being updated
// you may want to handle this differently
throw new Error(JSON.stringify(error));
}
return;
}
// When a user is deleted - delete the supabase user
if (operation === 'delete') {
const supabaseSudoClient = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!
);
try {
await supabaseSudoClient.auth.admin.deleteUser(item.subjectId);
} catch (error) {
console.log(error);
}
return;
}
},
},
fields: {
name: text({ validation: { isRequired: true } }),
email: text({
validation: { isRequired: true },
isIndexed: 'unique',
access: {
// email only visible to authenticated users
read: permissions.authenticatedUser,
},
}),
subjectId: text({
validation: { isRequired: true },
isIndexed: 'unique',
access: {
// subjestId only visible to authenticated users
read: permissions.authenticatedUser,
},
}),
role: select({
options: [
{ label: 'Admin', value: 'admin' },
{ label: 'Editor', value: 'editor' },
{ label: 'User', value: 'user' },
],
}),
content: text(),
createdAt: timestamp({
defaultValue: { kind: 'now' },
}),
Expand Down
93 changes: 26 additions & 67 deletions examples/nextjs-keystone-supabase-auth/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
# This file is automatically generated by Keystone, do not modify it manually.
# Modify your Keystone config when you want to change this.

type User {
type Post {
id: ID!
name: String
email: String
subjectId: String
role: String
content: String
createdAt: DateTime
}

scalar DateTime @specifiedBy(url: "https://datatracker.ietf.org/doc/html/rfc3339#section-5.6")

input UserWhereUniqueInput {
input PostWhereUniqueInput {
id: ID
email: String
subjectId: String
}

input UserWhereInput {
AND: [UserWhereInput!]
OR: [UserWhereInput!]
NOT: [UserWhereInput!]
input PostWhereInput {
AND: [PostWhereInput!]
OR: [PostWhereInput!]
NOT: [PostWhereInput!]
id: IDFilter
name: StringFilter
email: StringFilter
subjectId: StringFilter
role: StringNullableFilter
content: StringFilter
createdAt: DateTimeNullableFilter
}

Expand Down Expand Up @@ -75,35 +69,6 @@ input NestedStringFilter {
not: NestedStringFilter
}

input StringNullableFilter {
equals: String
in: [String!]
notIn: [String!]
lt: String
lte: String
gt: String
gte: String
contains: String
startsWith: String
endsWith: String
mode: QueryMode
not: NestedStringNullableFilter
}

input NestedStringNullableFilter {
equals: String
in: [String!]
notIn: [String!]
lt: String
lte: String
gt: String
gte: String
contains: String
startsWith: String
endsWith: String
not: NestedStringNullableFilter
}

input DateTimeNullableFilter {
equals: DateTime
in: [DateTime!]
Expand All @@ -115,12 +80,10 @@ input DateTimeNullableFilter {
not: DateTimeNullableFilter
}

input UserOrderByInput {
input PostOrderByInput {
id: OrderDirection
name: OrderDirection
email: OrderDirection
subjectId: OrderDirection
role: OrderDirection
content: OrderDirection
createdAt: OrderDirection
}

Expand All @@ -129,24 +92,20 @@ enum OrderDirection {
desc
}

input UserUpdateInput {
input PostUpdateInput {
name: String
email: String
subjectId: String
role: String
content: String
createdAt: DateTime
}

input UserUpdateArgs {
where: UserWhereUniqueInput!
data: UserUpdateInput!
input PostUpdateArgs {
where: PostWhereUniqueInput!
data: PostUpdateInput!
}

input UserCreateInput {
input PostCreateInput {
name: String
email: String
subjectId: String
role: String
content: String
createdAt: DateTime
}

Expand All @@ -156,18 +115,18 @@ The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://
scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")

type Mutation {
createUser(data: UserCreateInput!): User
createUsers(data: [UserCreateInput!]!): [User]
updateUser(where: UserWhereUniqueInput!, data: UserUpdateInput!): User
updateUsers(data: [UserUpdateArgs!]!): [User]
deleteUser(where: UserWhereUniqueInput!): User
deleteUsers(where: [UserWhereUniqueInput!]!): [User]
createPost(data: PostCreateInput!): Post
createPosts(data: [PostCreateInput!]!): [Post]
updatePost(where: PostWhereUniqueInput!, data: PostUpdateInput!): Post
updatePosts(data: [PostUpdateArgs!]!): [Post]
deletePost(where: PostWhereUniqueInput!): Post
deletePosts(where: [PostWhereUniqueInput!]!): [Post]
}

type Query {
users(where: UserWhereInput! = {}, orderBy: [UserOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: UserWhereUniqueInput): [User!]
user(where: UserWhereUniqueInput!): User
usersCount(where: UserWhereInput! = {}): Int
posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: PostWhereUniqueInput): [Post!]
post(where: PostWhereUniqueInput!): Post
postsCount(where: PostWhereInput! = {}): Int
keystone: KeystoneMeta!
}

Expand Down
6 changes: 2 additions & 4 deletions examples/nextjs-keystone-supabase-auth/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ generator client {
output = "node_modules/.myprisma/client"
}

model User {
model Post {
id String @id @default(cuid())
name String @default("")
email String @unique @default("")
subjectId String @unique @default("")
role String?
content String @default("")
createdAt DateTime? @default(now())
}

0 comments on commit cc4be55

Please sign in to comment.