Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding Nhost as database provider #94

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ REDIS_SSL_ENABLED=false
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_SECRET=

# If you’d like to use Nhost to store user data in the API routes,
# fill these below. See: lib/db-providers/nhost.ts
NHOST_SUBDOMAIN=
NHOST_REGION=
NHOST_ADMIN_SECRET=

# will be used to create a hash of the email address, which will be used for the Redis key for # each user data (i.e. id:<hash>). See lib/redis.ts for details.
EMAIL_TO_ID_SECRET=

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,4 @@ Environment variables determine which database to use. See [lib/db-api.ts](http

- [Redis](https://github.com/vercel/virtual-event-starter-kit/blob/main/lib/db-providers/README.md#redis)
- [Supabase](https://github.com/vercel/virtual-event-starter-kit/blob/main/lib/db-providers/README.md#supabase)
- [Nhost](https://github.com/vercel/virtual-event-starter-kit/blob/main/lib/db-providers/README.md#nhost)
8 changes: 8 additions & 0 deletions lib/db-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SAMPLE_TICKET_NUMBER } from '@lib/constants';

import * as redisApi from './db-providers/redis';
import * as supabaseApi from './db-providers/supabase';
import * as nhostApi from './db-providers/nhost';

let dbApi: {
createUser: (id: string, email: string) => Promise<ConfUser>;
Expand All @@ -36,6 +37,13 @@ if (process.env.REDIS_PORT && process.env.REDIS_URL && process.env.EMAIL_TO_ID_S
process.env.EMAIL_TO_ID_SECRET
) {
dbApi = supabaseApi;
} else if (
process.env.NHOST_SUBDOMAIN &&
process.env.NHOST_REGION &&
process.env.NHOST_ADMIN_SECRET &&
process.env.EMAIL_TO_ID_SECRET
) {
dbApi = nhostApi;
} else {
dbApi = {
createUser: () => Promise.resolve({ ticketNumber: SAMPLE_TICKET_NUMBER }),
Expand Down
13 changes: 12 additions & 1 deletion lib/db-providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,16 @@ If you do not want to maintain a Redis server, you can use [Upstash](https://up

- If you haven't already, [create a Supabase account](https://app.supabase.com/) and project.
- Within your project, navigate to the [SQL editor](https://app.supabase.com/project/_/sql) and create a "New query".
- Copy the SQL from [supabase/schema.sql](https://github.com/vercel/virtual-event-starter-kit/blob/main/lib/db-providers/schema.sql) and paste it into the Supabase SQL editor and click run.
- Copy the SQL from [supabase/schema.sql](https://github.com/vercel/virtual-event-starter-kit/blob/main/lib/db-providers/supabase/schema.sql) and paste it into the Supabase SQL editor and click run.
- Navigate to the [API settings](https://app.supabase.com/project/_/settings/api) and copy the project URL and service role key (make sure to keep it secret) to your env variables.

## Nhost

[Nhost](https://nhost.io/) provides a cloud hosted Postgres database which can be accessed via an auto-generated GraphQL API.

### **Setup**

- If you haven't already, [create a Nhost account](https://app.nhost.io/) and project.
- Open the Haura Console, navigate to the SQL editor.
- Copy the SQL from [nhost/schema.sql](https://github.com/vercel/virtual-event-starter-kit/blob/main/lib/db-providers/nhost/schema.sql) and paste it into the SQL editor and click run.
- Navigate to the Nhost Dashboard, and copy your Nhost project's `subdomain`, `region` and `admin secret` to your env variables (`.env.local`).
218 changes: 218 additions & 0 deletions lib/db-providers/nhost/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/**
* Copyright 2020 Vercel Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfUser } from '@lib/types';
import { NhostClient } from '@nhost/nhost-js';

const nhost =
process.env.NHOST_SUBDOMAIN && process.env.NHOST_REGION
? new NhostClient({
subdomain: process.env.NHOST_SUBDOMAIN,
region: process.env.NHOST_REGION,
adminSecret: process.env.NHOST_ADMIN_SECRET
})
: undefined;

export async function getUserByUsername(username: string): Promise<ConfUser> {
if (!nhost) {
throw new Error('Nhost client not initialized');
}

const QUERY = `#graphql
query getUserByUsername($username: String!) {
conference_users (where: { username: { _eq: $username } }) {
id
email
ticketNumber
name
username
createdAt
}
}
`;

const { data, error } = await nhost.graphql.request(QUERY, { username });

if (error) {
throw error;
}

return data.conference_users[0] ?? {};
}

export async function getUserById(id: string): Promise<ConfUser> {
if (!nhost) {
throw new Error('Nhost client not initialized');
}

const QUERY = `#graphql
query getConferenceUserById($id: String!) {
conference_user: conference_users_by_pk(id: $id) {
id
email
ticketNumber
name
username
createdAt
}
}
`;

const { data, error } = await nhost.graphql.request(QUERY, { id });

if (error) {
throw error;
}

return data.conference_user ?? {};
}

export async function createUser(id: string, email: string): Promise<ConfUser> {
if (!nhost) {
throw new Error('Nhost client not initialized');
}

const MUTATION = `#graphql
mutation insertConferenceUser($conferenceUser: conference_users_insert_input!) {
inserted_conference_user: insert_conference_users_one(object: $conferenceUser) {
id
email
ticketNumber
name
username
createdAt
}
}
`;

const { data, error } = await nhost.graphql.request(MUTATION, { conferenceUser: { id, email } });

if (error) {
throw error;
}

return data.inserted_conference_user ?? {};
}

export async function getTicketNumberByUserId(id: string): Promise<string | null> {
if (!nhost) {
throw new Error('Nhost client not initialized');
}

const QUERY = `#graphql
query getConferenceUserById($id: String!) {
conference_user: conference_users_by_pk(id: $id) {
id
email
ticketNumber
name
username
createdAt
}
}
`;

const { data, error } = await nhost.graphql.request(QUERY, { id });

if (error) {
throw error;
}

return data?.conference_user?.ticketNumber.toString() ?? null;
}

export async function createGitHubUser(user: any): Promise<string> {
if (!nhost) {
throw new Error('Nhost client not initialized');
}

const MUTATION = `#graphql
mutation insertConferenceGitHubUser($conferenceGitHubUser: conference_github_users_insert_input!) {
inserted_conference_github_user: insert_conference_github_users_one(object: $conferenceGitHubUser) {
id
}
}
`;

const { data, error } = await nhost.graphql.request(MUTATION, {
conferenceGitHubUser: {
userData: {
user
}
}
});

if (error) {
throw error;
}

return data.inserted_conference_github_user.id;
}

export async function updateUserWithGitHubUser(id: string, token: string): Promise<ConfUser> {
if (!nhost) {
throw new Error('Nhost client not initialized');
}

// `token` is the conference github user id
// `id` is the conference user id

// get github user (login (username) and name)
const GET_GITHUB_USER = `#graphql
query getGitHubUser($id: uuid!) {
conferenceGitHubUser: conference_github_users_by_pk(id: $id) {
userData
}
}
`;

const { data, error } = await nhost.graphql.request(GET_GITHUB_USER, { id: token });

if (error) {
console.error(error);
throw error;
}

const { conferenceGitHubUser } = data;

if (!conferenceGitHubUser) {
console.error('invalid or expired token');
throw new Error('Invalid or expired token');
}

// extract github user data
const { login: username, name } = conferenceGitHubUser.userData.user;

// update conference user with github user data
const UPDATE_CONFERENCE_USER = `#graphql
mutation updateConferenceUser($id: String!, $conferenceUser: conference_users_set_input!) {
update_conference_users_by_pk(pk_columns: {id: $id}, _set: $conferenceUser) {
id
}
}
`;

const { error: updateUserError } = await nhost.graphql.request(UPDATE_CONFERENCE_USER, {
id,
conferenceUser: { username, name }
});

if (updateUserError) {
console.error(updateUserError);
throw updateUserError;
}

return { username, name };
}
14 changes: 14 additions & 0 deletions lib/db-providers/nhost/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
create table conference_users (
id text primary key,
email text unique,
"ticketNumber" bigserial,
name text,
username text unique,
"createdAt" timestamp with time zone default timezone('utc'::text, now()) not null
);

create table conference_github_users (
id uuid primary key default gen_random_uuid(),
"createdAt" timestamp with time zone default timezone('utc'::text, now()) not null,
"userData" jsonb
);
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@agility/content-fetch": "1.1.1",
"@agility/content-sync": "1.0.3",
"@hcaptcha/react-hcaptcha": "^0.3.7",
"@nhost/nhost-js": "^1.4.10",
"@radix-ui/react-dialog": "0.1.5",
"@radix-ui/react-dropdown-menu": "0.1.4",
"@radix-ui/react-tabs": "0.1.4",
Expand All @@ -30,6 +31,7 @@
"classnames": "2.2.6",
"cookie": "0.4.1",
"date-fns": "2.16.1",
"graphql": "^16.6.0",
"htmlescape": "1.1.1",
"intersection-observer": "0.12.0",
"ioredis": "4.19.4",
Expand Down
2 changes: 1 addition & 1 deletion pages/api/github-oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default async function githubOAuth(req: NextApiRequest, res: NextApiRespo
try {
const token = await createGitHubUser(user);
res.end(renderSuccess({ type: 'token', token }));
} catch {
} catch (error) {
res.end(renderSuccess({ type: 'user', login: user.login, name: user.name }));
}
}
Loading