Skip to content

Commit

Permalink
remove fauna graphql api (#170)
Browse files Browse the repository at this point in the history
* remove reliance on fauna GQL API. revert to older CRUD style API

* add my projects endpoint

* add CI=false

* add date created to preview
  • Loading branch information
ejarzo authored Mar 18, 2024
1 parent 49b8e17 commit d1160e4
Show file tree
Hide file tree
Showing 12 changed files with 604 additions and 99 deletions.
36 changes: 36 additions & 0 deletions functions/all-projects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
getFauna,
getProjectPreviewData,
withErrorWrapper,
} from './utils/fauna';

exports.handler = withErrorWrapper(async (event, context) => {
const { before, after, size = 24 } = event.queryStringParameters;
const { q, client } = getFauna();
const result = await client.query(
q.Paginate(q.Match(q.Index('all_projects_sorted_by_date_created')), {
size,
after: after ? parseInt(after) : undefined,
before: before ? parseInt(before) : undefined,
})
);
const getAllprojectDataQuery = result.data.map(ref => q.Get(ref[1]));
const allProjects = await client.query(getAllprojectDataQuery);

return {
statusCode: 200,
body: JSON.stringify({
after: result.after,
before: result.before,
data: allProjects.map(({ ref, data, ts }, i) => {
return {
ref,
ts,
_id: ref.id,
index: i,
...getProjectPreviewData(data),
};
}),
}),
};
});
46 changes: 46 additions & 0 deletions functions/my-projects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
getFauna,
getProjectPreviewData,
withErrorWrapper,
} from './utils/fauna';

exports.handler = withErrorWrapper(async (event, context) => {
const { user } = context.clientContext;
if (!user) {
return { statusCode: 401, body: 'Not logged in' };
}

// const { before, after, size = 24 } = event.queryStringParameters;
const { q, client } = getFauna();
console.log('USER', user);
const result = await client.query(
q.Paginate(
q.Match(q.Index('projectsByUserId'), user.sub)
// {
// size,
// after: after ? [parseInt(after)] : undefined,
// before: before ? [parseInt(before)] : undefined,
// }
)
);

const getAllprojectDataQuery = result.data.map(ref => q.Get(ref));
const allProjects = await client.query(getAllprojectDataQuery);

return {
statusCode: 200,
body: JSON.stringify({
after: result.after,
before: result.before,
data: allProjects.map(({ ref, data, ts }, i) => {
return {
ref,
ts,
_id: ref.id,
index: i,
...getProjectPreviewData(data),
};
}),
}),
};
});
80 changes: 80 additions & 0 deletions functions/project.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { getFauna, getId, withErrorWrapper } from './utils/fauna';

exports.handler = withErrorWrapper(async (event, context) => {
const method = event.httpMethod;
const { q, client } = getFauna();
const id = getId(event.path);
const { user } = context.clientContext;
if (!id) return { statusCode: 400, body: 'invalid request' };

console.log(`Function 'project' invoked. Method: ${method}, Read id: ${id}`);
const projectIdPath = `classes/Project/${id}`;

const verifyProjectOwner = async (id, user) => {
if (!user) {
throw new Error('Not logged in');
}
const project = await client.query(q.Get(q.Ref(projectIdPath)));
if (!project) {
throw new Error('Project not found');
}
const { userId } = project.data;
if (userId !== user.sub) {
throw new Error('Unauthorized');
}
};

switch (method) {
case 'GET': {
const response = await client.query(q.Get(q.Ref(projectIdPath)));
return { statusCode: 200, body: JSON.stringify(response) };
}

case 'POST': {
if (!user) {
return { statusCode: 401, body: 'Not logged in' };
}
const userId = user.sub;
const userName = user.user_metadata.full_name;
const dateCreated = Date.now() * 1000;
const data = JSON.parse(event.body);
const response = await client.query(
q.Create(q.Ref('classes/Project'), {
data: { ...data, userId, userName, dateCreated },
})
);
return {
statusCode: 200,
body: JSON.stringify({
data: { ...response.data, _id: response.ref.id },
}),
};
}

case 'PATCH': {
try {
await verifyProjectOwner(id, user);
} catch (err) {
return { statusCode: 401, body: JSON.stringify(err) };
}
const data = JSON.parse(event.body);
const response = await client.query(
q.Update(q.Ref(projectIdPath), { data })
);
return { statusCode: 200, body: JSON.stringify(response) };
}

case 'DELETE': {
try {
await verifyProjectOwner(id, user);
} catch (err) {
return { statusCode: 401, body: JSON.stringify(err) };
}
const response = await client.query(q.Delete(q.Ref(projectIdPath)));
return { statusCode: 200, body: JSON.stringify(response) };
}

default:
return { statusCode: 400, body: 'invalid method' };
}
});
7 changes: 7 additions & 0 deletions functions/utils/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export const initSentry = () => {
});
};

export const captureError = (error, context) => {
Sentry.withScope(scope => {
scope.setExtra('context', context);
Sentry.captureException(error);
});
};

export const objectMap = (object, mapper) =>
Object.entries(object).reduce(
(acc, [key, value]) => ({
Expand Down
51 changes: 51 additions & 0 deletions functions/utils/fauna.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import faunadb from 'faunadb';
import * as Sentry from '@sentry/node';
import { initSentry, captureError } from './errors';

initSentry();

export const getFauna = () => ({
q: faunadb.query,
client: new faunadb.Client({
secret: process.env.FAUNADB_SERVER_SECRET,
}),
});

export const getId = urlPath => urlPath.match(/([^\/]*)\/*$/)[0];
export const getUserName = user => user && user.user_metadata.full_name;

export const getProjectPreviewData = ({
name,
userName,
shapesList,
dateCreated,
}) => {
return {
name,
userName,
dateCreated,
shapesList,
};
};

export const withErrorWrapper = callback => async (event, context) => {
try {
return await callback(event, context);
} catch (err) {
console.log('GOT ERROR', err.name);
console.log('ERROR MESSAGE:', err.message);

captureError(err, context);
try {
return {
statusCode: err.requestResult.statusCode,
body: JSON.stringify(err),
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify(err),
};
}
}
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"color": "^2.0.1",
"craco-antd": "^1.19.0",
"date-fns": "^2.0.1",
"faunadb": "2.6.1",
"file-saver": "2.0.0",
"graphql": "^14.5.8",
"graphql-request": "^1.8.2",
Expand Down Expand Up @@ -64,7 +65,7 @@
"upload-schema": "yarn checkForFaunaKey && curl -u $FAUNADB_SERVER_SECRET: https://graphql.fauna.com/import --data-binary \"@schema.gql\"",
"start:server": "yarn upload-schema && netlify-lambda serve functions -c ./webpack.config.js",
"start:devserver": "NODE_ENV=development yarn start:server",
"build": "npm-run-all --parallel build:**",
"build": "CI=false npm-run-all --parallel build:**",
"build:app": "craco build",
"build:functions": "netlify-lambda build functions -c ./webpack.config.js",
"test": "craco test --env=jsdom",
Expand Down
58 changes: 58 additions & 0 deletions src/utils/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import netlifyIdentity from 'netlify-identity-widget';
const API_URL = `/.netlify/functions`;

export const apiDeleteProject = async id => {
const url = `${API_URL}/project/${id}`;
return fetcher(url, { method: 'DELETE' });
};

export const apiPatchProject = async (id, data) => {
const url = `${API_URL}/project/${id}`;
return fetcher(url, {
method: 'PATCH',
body: JSON.stringify(data),
});
};

export const apiPostProject = async data => {
const url = `${API_URL}/project`;
return fetcher(url, {
method: 'POST',
body: JSON.stringify(data),
});
};

export const fetchProject = async id => {
const url = `${API_URL}/project/${id}`;
return fetcher(url);
};

export const fetchAllProjects = async pagination => {
const queryParams = new URLSearchParams(pagination);
const url = `${API_URL}/all-projects?${queryParams}`;
return fetcher(url);
};

export const fetchMyProjects = async pagination => {
const queryParams = new URLSearchParams(pagination);
const url = `${API_URL}/my-projects?${queryParams}`;
return fetcher(url);
};

export const fetcher = async (url, opts) => {
const currentUser = netlifyIdentity.currentUser();
let token;
if (currentUser) {
token = await currentUser.jwt();
}
const res = await fetch(url, {
headers: {
authorization: token ? `Bearer ${token}` : '',
},
...opts,
});
if (!res.ok) {
throw new Error(res.statusText);
}
return res.json();
};
39 changes: 21 additions & 18 deletions src/views/DiscoverGQL/index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
import React, { useState } from 'react';
import { useQuery } from '@apollo/react-hooks';

import React, { useEffect, useState } from 'react';
import ProjectList from 'components/ProjectList';
import Loading from 'components/Loading';
import PageContainer from 'components/PageContainer';
import { GET_ALL_PROJECTS_SORTED_BY_DATE_CREATED } from 'graphql/queries';
import ErrorMessage from 'components/ErrorMessage';
import { fetchAllProjects } from 'utils/middleware';

function DiscoverGQLContainer() {
const pageSize = 24;
const [cursor, setCursor] = useState(null);
const { loading, error, data } = useQuery(
GET_ALL_PROJECTS_SORTED_BY_DATE_CREATED,
{ variables: { _size: pageSize, _cursor: cursor } }
);
const [pagination, setPagination] = useState({});
const [{ loading, error, data }, setResult] = useState({ loading: true });

useEffect(() => {
setResult({ loading: true });
const fetchData = async () => {
try {
const result = await fetchAllProjects(pagination);
setResult({ loading: false, data: result });
} catch (error) {
setResult({ loading: false, error });
}
};
fetchData();
}, [pagination]);

if (loading) return <Loading />;
if (error) return <ErrorMessage message={error.message} />;
if (!data || loading) return <Loading />;

const {
before,
after,
data: projectsData,
} = data.allProjectsSortedByDateCreated;
const { before, after, data: projectsData } = data;

const onNextPageClick = () => {
setCursor(after);
setPagination({ after });
};
const onPrevPageClick = () => {
setCursor(before);
setPagination({ before });
};

return (
Expand Down
Loading

0 comments on commit d1160e4

Please sign in to comment.