From 3721586c00b7821dacd4b9fe1bd9a9177cdcc185 Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:16:58 -0600 Subject: [PATCH 01/13] add avatar upload component --- @app/components/src/AvatarUpload.tsx | 208 +++++++++++++++++++++++++++ @app/components/src/index.tsx | 1 + 2 files changed, 209 insertions(+) create mode 100644 @app/components/src/AvatarUpload.tsx diff --git a/@app/components/src/AvatarUpload.tsx b/@app/components/src/AvatarUpload.tsx new file mode 100644 index 00000000..fbd920c2 --- /dev/null +++ b/@app/components/src/AvatarUpload.tsx @@ -0,0 +1,208 @@ +import React, { useState, useEffect } from "react"; +import { Upload, Icon, message } from "antd"; +import { UploadChangeParam } from "antd/lib/upload"; +import { UploadFile, RcCustomRequestOptions } from "antd/lib/upload/interface"; +import axios from "axios"; +import { + useChangeAvatarMutation, + ProfileSettingsForm_UserFragment, +} from "@app/graphql"; +import { ApolloError } from "apollo-client"; + +export function slugify(string: string) { + const a = + "àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;"; + const b = + "aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz------"; + const p = new RegExp(a.split("").join("|"), "g"); + + return string + .toString() + .toLowerCase() + .replace(/\s+/g, "-") // Replace spaces with - + .replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters + .replace(/&/g, "-and-") // Replace & with 'and' + .replace(/[^\w\-]+/g, "") // Remove all non-word characters + .replace(/\-\-+/g, "-") // Replace multiple - with single - + .replace(/^-+/, "") // Trim - from start of text + .replace(/-+$/, ""); // Trim - from end of text +} + +export function getUid(name: string) { + const randomHex = () => Math.floor(Math.random() * 16777215).toString(16); + const fileNameSlug = slugify(name); + return randomHex() + "-" + fileNameSlug; +} + +export function AvatarUpload({ + user, + setSuccess, + setError, +}: { + user: ProfileSettingsForm_UserFragment; + setSuccess: React.Dispatch>; + setError: (error: Error | ApolloError | null) => void; +}) { + const [changeAvatar] = useChangeAvatarMutation(); + const [fileList, setFileList] = useState( + user && user.avatarUrl + ? [ + { + uid: "-1", + name: "avatar", + type: "image", + size: 1, + url: user.avatarUrl, + }, + ] + : null + ); + + useEffect(() => { + if (user) { + const avatar = user.avatarUrl; + if (avatar) { + setFileList([ + { + uid: "-1", + name: "avatar", + type: "image", + size: 1, + url: avatar, + }, + ]); + } else { + setFileList(null); + } + } + }, [user, user.avatarUrl]); + + // const onChange = (info: UploadChangeParam) => { + // console.log(info); + // setFileList([...fileList]); + // }; + + const beforeUpload = (file: any) => { + const fileName = file.name.split(".")[0]; + const fileType = file.name.split(".")[1]; + file.uid = getUid(fileName) + "." + fileType; + const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png"; + if (!isJpgOrPng) { + message.error("You can only upload JPG or PNG images!"); + file.status = "error"; + } + const isLt3M = file.size / 1024 / 1024 < 3; + if (!isLt3M) { + message.error("Image must smaller than 3MB!"); + file.status = "error"; + } + return isJpgOrPng && isLt3M; + }; + + const changeUserAvatar = async (avatarUrl: string | null) => { + setSuccess(false); + setError(null); + try { + await changeAvatar({ + variables: { + id: user.id, + patch: { + avatarUrl, + }, + }, + }); + setError(null); + setSuccess(true); + } catch (e) { + setError(e); + } + }; + + const customRequest = (option: RcCustomRequestOptions) => { + const { onSuccess, onError, file, onProgress } = option; + axios + .get(`${process.env.ROOT_URL}/api/s3`, { + params: { + key: file.uid, + operation: "put", + }, + }) + .then(response => { + const preSignedUrl = response.data.url; + axios + .put(preSignedUrl, file, { + onUploadProgress: e => { + const progress = Math.round((e.loaded / e.total) * 100); + onProgress({ percent: progress }, file); + }, + }) + .then(response => { + if (response.config.url) { + changeUserAvatar(response.config.url.split("?")[0]); + onSuccess(response.config, file); + } + }) + .catch(error => { + console.log(error); + onError(error); + }); + }) + .catch(error => { + console.log(error); + onError(error); + }); + }; + + const deleteUserAvatarFromBucket = async () => { + if (user && user.avatarUrl) { + const key = user.avatarUrl.substring(user.avatarUrl.lastIndexOf("/") + 1); + await axios + .get(`${process.env.ROOT_URL}/api/s3`, { + params: { + key: `${key}`, + operation: "delete", + }, + }) + .then(() => { + // this isn't confirmation that the item was deleted + // only confimation that there wasnt an error.. + changeUserAvatar(null); + return true; + }) + .catch(error => { + console.log(JSON.stringify(error)); + return false; + }); + } + return true; + }; + + const onRemove = async () => { + if (await deleteUserAvatarFromBucket()) { + setFileList(null); + } + }; + + const uploadButton = ( +
+ +
Avatar
+
+ ); + + return ( +
+ + {fileList && fileList.length >= 0 ? null : uploadButton} + +
+ ); +} diff --git a/@app/components/src/index.tsx b/@app/components/src/index.tsx index c178579a..cfa3daa3 100644 --- a/@app/components/src/index.tsx +++ b/@app/components/src/index.tsx @@ -5,3 +5,4 @@ export * from "./StandardWidth"; export * from "./Text"; export * from "./Warn"; export * from "./PasswordStrength"; +export * from "./AvatarUpload"; From 11e47960ce5c5bed5d707046cf066ac0782d9d02 Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:17:45 -0600 Subject: [PATCH 02/13] schema auto change --- data/schema.sql | 118 ++++++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/data/schema.sql b/data/schema.sql index b9eb40d2..82be48b8 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 11.6 (Debian 11.6-1.pgdg90+1) --- Dumped by pg_dump version 11.6 (Debian 11.6-1.pgdg90+1) +-- Dumped from database version 12.1 +-- Dumped by pg_dump version 12.1 SET statement_timeout = 0; SET lock_timeout = 0; @@ -97,7 +97,7 @@ $$; SET default_tablespace = ''; -SET default_with_oids = false; +SET default_table_access_method = heap; -- -- Name: users; Type: TABLE; Schema: app_public; Owner: - @@ -1573,70 +1573,70 @@ CREATE INDEX user_authentications_user_id_idx ON app_public.user_authentications -- Name: user_authentications _100_timestamps; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _100_timestamps BEFORE INSERT OR UPDATE ON app_public.user_authentications FOR EACH ROW EXECUTE PROCEDURE app_private.tg__timestamps(); +CREATE TRIGGER _100_timestamps BEFORE INSERT OR UPDATE ON app_public.user_authentications FOR EACH ROW EXECUTE FUNCTION app_private.tg__timestamps(); -- -- Name: user_emails _100_timestamps; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _100_timestamps BEFORE INSERT OR UPDATE ON app_public.user_emails FOR EACH ROW EXECUTE PROCEDURE app_private.tg__timestamps(); +CREATE TRIGGER _100_timestamps BEFORE INSERT OR UPDATE ON app_public.user_emails FOR EACH ROW EXECUTE FUNCTION app_private.tg__timestamps(); -- -- Name: users _100_timestamps; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _100_timestamps BEFORE INSERT OR UPDATE ON app_public.users FOR EACH ROW EXECUTE PROCEDURE app_private.tg__timestamps(); +CREATE TRIGGER _100_timestamps BEFORE INSERT OR UPDATE ON app_public.users FOR EACH ROW EXECUTE FUNCTION app_private.tg__timestamps(); -- -- Name: user_emails _200_forbid_existing_email; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _200_forbid_existing_email BEFORE INSERT ON app_public.user_emails FOR EACH ROW EXECUTE PROCEDURE app_public.tg_user_emails__forbid_if_verified(); +CREATE TRIGGER _200_forbid_existing_email BEFORE INSERT ON app_public.user_emails FOR EACH ROW EXECUTE FUNCTION app_public.tg_user_emails__forbid_if_verified(); -- -- Name: users _200_make_first_user_admin; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _200_make_first_user_admin BEFORE INSERT ON app_public.users FOR EACH ROW WHEN ((new.id = 1)) EXECUTE PROCEDURE app_private.tg_users__make_first_user_admin(); +CREATE TRIGGER _200_make_first_user_admin BEFORE INSERT ON app_public.users FOR EACH ROW WHEN ((new.id = 1)) EXECUTE FUNCTION app_private.tg_users__make_first_user_admin(); -- -- Name: users _500_gql_update; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _500_gql_update AFTER UPDATE ON app_public.users FOR EACH ROW EXECUTE PROCEDURE app_public.tg__graphql_subscription('userChanged', 'graphql:user:$1', 'id'); +CREATE TRIGGER _500_gql_update AFTER UPDATE ON app_public.users FOR EACH ROW EXECUTE FUNCTION app_public.tg__graphql_subscription('userChanged', 'graphql:user:$1', 'id'); -- -- Name: user_emails _500_insert_secrets; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _500_insert_secrets AFTER INSERT ON app_public.user_emails FOR EACH ROW EXECUTE PROCEDURE app_private.tg_user_email_secrets__insert_with_user_email(); +CREATE TRIGGER _500_insert_secrets AFTER INSERT ON app_public.user_emails FOR EACH ROW EXECUTE FUNCTION app_private.tg_user_email_secrets__insert_with_user_email(); -- -- Name: users _500_insert_secrets; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _500_insert_secrets AFTER INSERT ON app_public.users FOR EACH ROW EXECUTE PROCEDURE app_private.tg_user_secrets__insert_with_user(); +CREATE TRIGGER _500_insert_secrets AFTER INSERT ON app_public.users FOR EACH ROW EXECUTE FUNCTION app_private.tg_user_secrets__insert_with_user(); -- -- Name: user_emails _500_verify_account_on_verified; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _500_verify_account_on_verified AFTER INSERT OR UPDATE OF is_verified ON app_public.user_emails FOR EACH ROW WHEN ((new.is_verified IS TRUE)) EXECUTE PROCEDURE app_public.tg_user_emails__verify_account_on_verified(); +CREATE TRIGGER _500_verify_account_on_verified AFTER INSERT OR UPDATE OF is_verified ON app_public.user_emails FOR EACH ROW WHEN ((new.is_verified IS TRUE)) EXECUTE FUNCTION app_public.tg_user_emails__verify_account_on_verified(); -- -- Name: user_emails _900_send_verification_email; Type: TRIGGER; Schema: app_public; Owner: - -- -CREATE TRIGGER _900_send_verification_email AFTER INSERT ON app_public.user_emails FOR EACH ROW WHEN ((new.is_verified IS FALSE)) EXECUTE PROCEDURE app_private.tg__add_job('user_emails__send_verification'); +CREATE TRIGGER _900_send_verification_email AFTER INSERT ON app_public.user_emails FOR EACH ROW WHEN ((new.is_verified IS FALSE)) EXECUTE FUNCTION app_private.tg__add_job('user_emails__send_verification'); -- @@ -1788,14 +1788,14 @@ ALTER TABLE app_public.users ENABLE ROW LEVEL SECURITY; -- Name: SCHEMA app_hidden; Type: ACL; Schema: -; Owner: - -- -GRANT USAGE ON SCHEMA app_hidden TO graphile_starter_visitor; +GRANT USAGE ON SCHEMA app_hidden TO graphile_starter_photo_upload_visitor; -- -- Name: SCHEMA app_public; Type: ACL; Schema: -; Owner: - -- -GRANT USAGE ON SCHEMA app_public TO graphile_starter_visitor; +GRANT USAGE ON SCHEMA app_public TO graphile_starter_photo_upload_visitor; -- @@ -1803,8 +1803,8 @@ GRANT USAGE ON SCHEMA app_public TO graphile_starter_visitor; -- REVOKE ALL ON SCHEMA public FROM PUBLIC; -GRANT ALL ON SCHEMA public TO graphile_starter; -GRANT USAGE ON SCHEMA public TO graphile_starter_visitor; +GRANT ALL ON SCHEMA public TO graphile_starter_photo_upload; +GRANT USAGE ON SCHEMA public TO graphile_starter_photo_upload_visitor; -- @@ -1818,28 +1818,28 @@ REVOKE ALL ON FUNCTION app_private.assert_valid_password(new_password text) FROM -- Name: TABLE users; Type: ACL; Schema: app_public; Owner: - -- -GRANT SELECT ON TABLE app_public.users TO graphile_starter_visitor; +GRANT SELECT ON TABLE app_public.users TO graphile_starter_photo_upload_visitor; -- -- Name: COLUMN users.username; Type: ACL; Schema: app_public; Owner: - -- -GRANT UPDATE(username) ON TABLE app_public.users TO graphile_starter_visitor; +GRANT UPDATE(username) ON TABLE app_public.users TO graphile_starter_photo_upload_visitor; -- -- Name: COLUMN users.name; Type: ACL; Schema: app_public; Owner: - -- -GRANT UPDATE(name) ON TABLE app_public.users TO graphile_starter_visitor; +GRANT UPDATE(name) ON TABLE app_public.users TO graphile_starter_photo_upload_visitor; -- -- Name: COLUMN users.avatar_url; Type: ACL; Schema: app_public; Owner: - -- -GRANT UPDATE(avatar_url) ON TABLE app_public.users TO graphile_starter_visitor; +GRANT UPDATE(avatar_url) ON TABLE app_public.users TO graphile_starter_photo_upload_visitor; -- @@ -1910,7 +1910,7 @@ REVOKE ALL ON FUNCTION app_private.tg_users__make_first_user_admin() FROM PUBLIC -- REVOKE ALL ON FUNCTION app_public.change_password(old_password text, new_password text) FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.change_password(old_password text, new_password text) TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.change_password(old_password text, new_password text) TO graphile_starter_photo_upload_visitor; -- @@ -1918,7 +1918,7 @@ GRANT ALL ON FUNCTION app_public.change_password(old_password text, new_password -- REVOKE ALL ON FUNCTION app_public.confirm_account_deletion(token text) FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.confirm_account_deletion(token text) TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.confirm_account_deletion(token text) TO graphile_starter_photo_upload_visitor; -- @@ -1926,7 +1926,7 @@ GRANT ALL ON FUNCTION app_public.confirm_account_deletion(token text) TO graphil -- REVOKE ALL ON FUNCTION app_public.current_session_id() FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.current_session_id() TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.current_session_id() TO graphile_starter_photo_upload_visitor; -- @@ -1934,7 +1934,7 @@ GRANT ALL ON FUNCTION app_public.current_session_id() TO graphile_starter_visito -- REVOKE ALL ON FUNCTION app_public."current_user"() FROM PUBLIC; -GRANT ALL ON FUNCTION app_public."current_user"() TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public."current_user"() TO graphile_starter_photo_upload_visitor; -- @@ -1942,7 +1942,7 @@ GRANT ALL ON FUNCTION app_public."current_user"() TO graphile_starter_visitor; -- REVOKE ALL ON FUNCTION app_public.current_user_id() FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.current_user_id() TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.current_user_id() TO graphile_starter_photo_upload_visitor; -- @@ -1950,7 +1950,7 @@ GRANT ALL ON FUNCTION app_public.current_user_id() TO graphile_starter_visitor; -- REVOKE ALL ON FUNCTION app_public.forgot_password(email public.citext) FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.forgot_password(email public.citext) TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.forgot_password(email public.citext) TO graphile_starter_photo_upload_visitor; -- @@ -1958,21 +1958,21 @@ GRANT ALL ON FUNCTION app_public.forgot_password(email public.citext) TO graphil -- REVOKE ALL ON FUNCTION app_public.logout() FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.logout() TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.logout() TO graphile_starter_photo_upload_visitor; -- -- Name: TABLE user_emails; Type: ACL; Schema: app_public; Owner: - -- -GRANT SELECT,DELETE ON TABLE app_public.user_emails TO graphile_starter_visitor; +GRANT SELECT,DELETE ON TABLE app_public.user_emails TO graphile_starter_photo_upload_visitor; -- -- Name: COLUMN user_emails.email; Type: ACL; Schema: app_public; Owner: - -- -GRANT INSERT(email) ON TABLE app_public.user_emails TO graphile_starter_visitor; +GRANT INSERT(email) ON TABLE app_public.user_emails TO graphile_starter_photo_upload_visitor; -- @@ -1980,7 +1980,7 @@ GRANT INSERT(email) ON TABLE app_public.user_emails TO graphile_starter_visitor; -- REVOKE ALL ON FUNCTION app_public.make_email_primary(email_id integer) FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.make_email_primary(email_id integer) TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.make_email_primary(email_id integer) TO graphile_starter_photo_upload_visitor; -- @@ -1988,7 +1988,7 @@ GRANT ALL ON FUNCTION app_public.make_email_primary(email_id integer) TO graphil -- REVOKE ALL ON FUNCTION app_public.request_account_deletion() FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.request_account_deletion() TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.request_account_deletion() TO graphile_starter_photo_upload_visitor; -- @@ -1996,7 +1996,7 @@ GRANT ALL ON FUNCTION app_public.request_account_deletion() TO graphile_starter_ -- REVOKE ALL ON FUNCTION app_public.resend_email_verification_code(email_id integer) FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.resend_email_verification_code(email_id integer) TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.resend_email_verification_code(email_id integer) TO graphile_starter_photo_upload_visitor; -- @@ -2004,7 +2004,7 @@ GRANT ALL ON FUNCTION app_public.resend_email_verification_code(email_id integer -- REVOKE ALL ON FUNCTION app_public.reset_password(user_id integer, reset_token text, new_password text) FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.reset_password(user_id integer, reset_token text, new_password text) TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.reset_password(user_id integer, reset_token text, new_password text) TO graphile_starter_photo_upload_visitor; -- @@ -2012,7 +2012,7 @@ GRANT ALL ON FUNCTION app_public.reset_password(user_id integer, reset_token tex -- REVOKE ALL ON FUNCTION app_public.tg__graphql_subscription() FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.tg__graphql_subscription() TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.tg__graphql_subscription() TO graphile_starter_photo_upload_visitor; -- @@ -2020,7 +2020,7 @@ GRANT ALL ON FUNCTION app_public.tg__graphql_subscription() TO graphile_starter_ -- REVOKE ALL ON FUNCTION app_public.tg_user_emails__forbid_if_verified() FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.tg_user_emails__forbid_if_verified() TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.tg_user_emails__forbid_if_verified() TO graphile_starter_photo_upload_visitor; -- @@ -2028,7 +2028,7 @@ GRANT ALL ON FUNCTION app_public.tg_user_emails__forbid_if_verified() TO graphil -- REVOKE ALL ON FUNCTION app_public.tg_user_emails__verify_account_on_verified() FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.tg_user_emails__verify_account_on_verified() TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.tg_user_emails__verify_account_on_verified() TO graphile_starter_photo_upload_visitor; -- @@ -2036,7 +2036,7 @@ GRANT ALL ON FUNCTION app_public.tg_user_emails__verify_account_on_verified() TO -- REVOKE ALL ON FUNCTION app_public.users_has_password(u app_public.users) FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.users_has_password(u app_public.users) TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.users_has_password(u app_public.users) TO graphile_starter_photo_upload_visitor; -- @@ -2044,93 +2044,93 @@ GRANT ALL ON FUNCTION app_public.users_has_password(u app_public.users) TO graph -- REVOKE ALL ON FUNCTION app_public.verify_email(user_email_id integer, token text) FROM PUBLIC; -GRANT ALL ON FUNCTION app_public.verify_email(user_email_id integer, token text) TO graphile_starter_visitor; +GRANT ALL ON FUNCTION app_public.verify_email(user_email_id integer, token text) TO graphile_starter_photo_upload_visitor; -- -- Name: TABLE user_authentications; Type: ACL; Schema: app_public; Owner: - -- -GRANT SELECT,DELETE ON TABLE app_public.user_authentications TO graphile_starter_visitor; +GRANT SELECT,DELETE ON TABLE app_public.user_authentications TO graphile_starter_photo_upload_visitor; -- -- Name: SEQUENCE user_authentications_id_seq; Type: ACL; Schema: app_public; Owner: - -- -GRANT SELECT,USAGE ON SEQUENCE app_public.user_authentications_id_seq TO graphile_starter_visitor; +GRANT SELECT,USAGE ON SEQUENCE app_public.user_authentications_id_seq TO graphile_starter_photo_upload_visitor; -- -- Name: SEQUENCE user_emails_id_seq; Type: ACL; Schema: app_public; Owner: - -- -GRANT SELECT,USAGE ON SEQUENCE app_public.user_emails_id_seq TO graphile_starter_visitor; +GRANT SELECT,USAGE ON SEQUENCE app_public.user_emails_id_seq TO graphile_starter_photo_upload_visitor; -- -- Name: SEQUENCE users_id_seq; Type: ACL; Schema: app_public; Owner: - -- -GRANT SELECT,USAGE ON SEQUENCE app_public.users_id_seq TO graphile_starter_visitor; +GRANT SELECT,USAGE ON SEQUENCE app_public.users_id_seq TO graphile_starter_photo_upload_visitor; -- -- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: app_hidden; Owner: - -- -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_hidden REVOKE ALL ON SEQUENCES FROM graphile_starter; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_hidden GRANT SELECT,USAGE ON SEQUENCES TO graphile_starter_visitor; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_hidden REVOKE ALL ON SEQUENCES FROM graphile_starter_photo_upload; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_hidden GRANT SELECT,USAGE ON SEQUENCES TO graphile_starter_photo_upload_visitor; -- -- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: app_hidden; Owner: - -- -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_hidden REVOKE ALL ON FUNCTIONS FROM PUBLIC; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_hidden REVOKE ALL ON FUNCTIONS FROM graphile_starter; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_hidden GRANT ALL ON FUNCTIONS TO graphile_starter_visitor; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_hidden REVOKE ALL ON FUNCTIONS FROM PUBLIC; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_hidden REVOKE ALL ON FUNCTIONS FROM graphile_starter_photo_upload; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_hidden GRANT ALL ON FUNCTIONS TO graphile_starter_photo_upload_visitor; -- -- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: app_public; Owner: - -- -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_public REVOKE ALL ON SEQUENCES FROM graphile_starter; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_public GRANT SELECT,USAGE ON SEQUENCES TO graphile_starter_visitor; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_public REVOKE ALL ON SEQUENCES FROM graphile_starter_photo_upload; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_public GRANT SELECT,USAGE ON SEQUENCES TO graphile_starter_photo_upload_visitor; -- -- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: app_public; Owner: - -- -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_public REVOKE ALL ON FUNCTIONS FROM PUBLIC; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_public REVOKE ALL ON FUNCTIONS FROM graphile_starter; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA app_public GRANT ALL ON FUNCTIONS TO graphile_starter_visitor; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_public REVOKE ALL ON FUNCTIONS FROM PUBLIC; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_public REVOKE ALL ON FUNCTIONS FROM graphile_starter_photo_upload; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA app_public GRANT ALL ON FUNCTIONS TO graphile_starter_photo_upload_visitor; -- -- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: public; Owner: - -- -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA public REVOKE ALL ON SEQUENCES FROM graphile_starter; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA public GRANT SELECT,USAGE ON SEQUENCES TO graphile_starter_visitor; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA public REVOKE ALL ON SEQUENCES FROM graphile_starter_photo_upload; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA public GRANT SELECT,USAGE ON SEQUENCES TO graphile_starter_photo_upload_visitor; -- -- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: public; Owner: - -- -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM PUBLIC; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM graphile_starter; -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter IN SCHEMA public GRANT ALL ON FUNCTIONS TO graphile_starter_visitor; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM PUBLIC; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM graphile_starter_photo_upload; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload IN SCHEMA public GRANT ALL ON FUNCTIONS TO graphile_starter_photo_upload_visitor; -- -- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: -; Owner: - -- -ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter REVOKE ALL ON FUNCTIONS FROM PUBLIC; +ALTER DEFAULT PRIVILEGES FOR ROLE graphile_starter_photo_upload REVOKE ALL ON FUNCTIONS FROM PUBLIC; -- From 9d0bbbd2e8dafb56fa8cda6090c52a1330749695 Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:18:17 -0600 Subject: [PATCH 03/13] add axios --- @app/client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/@app/client/package.json b/@app/client/package.json index 0462f4b0..142b5f47 100644 --- a/@app/client/package.json +++ b/@app/client/package.json @@ -25,6 +25,7 @@ "apollo-link-error": "^1.1.11", "apollo-link-http": "^1.5.15", "apollo-link-ws": "^1.0.18", + "axios": "^0.19.1", "graphql": "^14.4.2", "less": "^3.9.0", "less-vars-to-js": "^1.3.0", From 02135d3f14f8c6e3532aa73ee4f99c7a349e8f46 Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:18:32 -0600 Subject: [PATCH 04/13] add change avatar mutation --- @app/client/src/graphql/ChangeAvatar.graphql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 @app/client/src/graphql/ChangeAvatar.graphql diff --git a/@app/client/src/graphql/ChangeAvatar.graphql b/@app/client/src/graphql/ChangeAvatar.graphql new file mode 100644 index 00000000..9f08f7b0 --- /dev/null +++ b/@app/client/src/graphql/ChangeAvatar.graphql @@ -0,0 +1,9 @@ +mutation ChangeAvatar($id: Int!, $patch: UserPatch!) { + updateUser(input: { id: $id, patch: $patch }) { + clientMutationId + user { + id + avatarUrl + } + } +} From 156e92ee5075fd1f256071268499eb7b8d587e5f Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:19:16 -0600 Subject: [PATCH 05/13] add cors. This is bad...I think --- @app/server/package.json | 2 ++ @app/server/src/app.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/@app/server/package.json b/@app/server/package.json index 461f5128..44a5f87f 100644 --- a/@app/server/package.json +++ b/@app/server/package.json @@ -16,6 +16,7 @@ "@types/chalk": "^2.2.0", "@types/connect-pg-simple": "^4.2.0", "@types/connect-redis": "^0.0.13", + "@types/cors": "^2.8.6", "@types/express-session": "^1.15.16", "@types/graphql": "*", "@types/helmet": "^0.0.45", @@ -28,6 +29,7 @@ "chalk": "^3.0.0", "connect-pg-simple": "^6.1.0", "connect-redis": "^4.0.3", + "cors": "^2.8.5", "express": "^4.17.1", "express-session": "^1.16.2", "graphile-build": "^4.5.0", diff --git a/@app/server/src/app.ts b/@app/server/src/app.ts index 64e14e0f..c7bcbde2 100644 --- a/@app/server/src/app.ts +++ b/@app/server/src/app.ts @@ -4,6 +4,7 @@ import * as middleware from "./middleware"; import { makeShutdownActions, ShutdownAction } from "./shutdownActions"; import { Middleware } from "postgraphile"; import { sanitizeEnv } from "./utils"; +import cors from "cors"; // Server may not always be supplied, e.g. where mounting on a sub-route export function getHttpServer(app: Express): Server | void { @@ -43,6 +44,8 @@ export async function makeApp({ */ const app = express(); + app.use(cors()); + /* * Getting access to the HTTP server directly means that we can do things * with websockets if we need to (e.g. GraphQL subscriptions). From 3644031e933eab147533609d89dfe6c19241767c Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:19:43 -0600 Subject: [PATCH 06/13] add aws env vars --- @app/client/src/next.config.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/@app/client/src/next.config.js b/@app/client/src/next.config.js index 312c98c0..3130800c 100644 --- a/@app/client/src/next.config.js +++ b/@app/client/src/next.config.js @@ -1,7 +1,14 @@ require("@app/config"); const compose = require("lodash/flowRight"); -const { ROOT_URL, T_AND_C_URL } = process.env; +const { + ROOT_URL, + T_AND_C_URL, + BUCKET, + AWSACCESSKEYID, + AWSSECRETKEY, + AWS_REGION, +} = process.env; if (!ROOT_URL) { throw new Error("ROOT_URL is a required envvar"); } @@ -31,6 +38,12 @@ if (!ROOT_URL) { withCss, withLess )({ + serverRuntimeConfig: { + BUCKET: BUCKET, + AWSACCESSKEYID: AWSACCESSKEYID, + AWSSECRETKEY: AWSSECRETKEY, + AWS_REGION: AWS_REGION, + }, poweredByHeader: false, distDir: `../.next`, exportTrailingSlash: true, From 5190ff5e96562e771c7c43cd796805d8417e9710 Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:20:00 -0600 Subject: [PATCH 07/13] add s3 api route --- @app/client/src/pages/api/s3.tsx | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 @app/client/src/pages/api/s3.tsx diff --git a/@app/client/src/pages/api/s3.tsx b/@app/client/src/pages/api/s3.tsx new file mode 100644 index 00000000..e65ba0d1 --- /dev/null +++ b/@app/client/src/pages/api/s3.tsx @@ -0,0 +1,67 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import AWS from "aws-sdk"; +import getConfig from "next/config"; +const { serverRuntimeConfig } = getConfig(); + +export default async (req: NextApiRequest, res: NextApiResponse) => { + const bucket = serverRuntimeConfig.BUCKET; + const key = req.query.key; + const params: AWS.S3.PutObjectRequest = { + Bucket: bucket, + Key: key as string, + }; + const client = getClient(); + const operation = req.query.operation; + if (operation === "put") { + put(client, params); + } else if (operation === "delete") { + del(client, params); + } + + function getClient() { + const region = serverRuntimeConfig.AWS_REGION; + const accessKey = serverRuntimeConfig.AWSACCESSKEYID; + const secretKey = serverRuntimeConfig.AWSSECRETKEY; + AWS.config.update({ + accessKeyId: accessKey, + secretAccessKey: secretKey, + signatureVersion: "v4", + region: region, + }); + const options = { + signatureVersion: "v4", + region: region, + // uncomment to use accelerated endpoint + // accelerated endpoint must be turned on in your s3 bucket first + // endpoint: new AWS.Endpoint( + // "bucket.s3-accelerate.amazonaws.com" + // ), + // useAccelerateEndpoint: true, + }; + const client = new AWS.S3(options); + return client; + } + function put(client: AWS.S3, params: AWS.S3.PutObjectRequest) { + const putParams = { + ...params, + Expires: 5 * 60, + }; + + client.getSignedUrl("putObject", putParams, (err, url) => { + if (err) { + res.json({ success: false, err }); + } else { + res.json({ success: true, url }); + } + }); + } + function del(client: AWS.S3, params: AWS.S3.DeleteObjectRequest) { + client.deleteObject(params, err => { + if (err) { + res.json({ success: false, err }); + } else { + res.json({ success: true }); + } + }); + } +}; From acaffb77d1228d6007d34cf3499b94bc2691da7c Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:20:33 -0600 Subject: [PATCH 08/13] use avatarPhoto if exists --- @app/client/src/layout/SharedLayout.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/@app/client/src/layout/SharedLayout.tsx b/@app/client/src/layout/SharedLayout.tsx index b25fdd8c..7051f945 100644 --- a/@app/client/src/layout/SharedLayout.tsx +++ b/@app/client/src/layout/SharedLayout.tsx @@ -132,9 +132,14 @@ function SharedLayout({ title, noPad = false, children }: SharedLayoutProps) { data-cy="layout-dropdown-user" style={{ whiteSpace: "nowrap" }} > - - {(data.currentUser.name && data.currentUser.name[0]) || "?"} - + {data.currentUser.avatarUrl ? ( + + ) : ( + + {(data.currentUser.name && data.currentUser.name[0]) || + "?"} + + )} {data.currentUser.name} From 40c4ffb371d26d10d8e27abdf34e38257b1b256f Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:20:58 -0600 Subject: [PATCH 09/13] yarn lock change --- yarn.lock | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c04a2d1e..6ec91a84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2521,6 +2521,13 @@ "@types/keygrip" "*" "@types/node" "*" +"@types/cors@^2.8.6": + version "2.8.6" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.6.tgz#cfaab33c49c15b1ded32f235111ce9123009bd02" + integrity sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg== + dependencies: + "@types/express" "*" + "@types/debounce@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.0.tgz#9ee99259f41018c640b3929e1bb32c3dcecdb192" @@ -3761,6 +3768,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c" integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A== +axios@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.1.tgz#8a6a04eed23dfe72747e1dd43c604b8f1677b5aa" + integrity sha512-Yl+7nfreYKaLRvAvjNPkvfjnQHJM1yLBY3zhqAwcJSwR/6ETkanUgylgtIvkvz0xJ+p/vZuNw8X7Hnb7Whsbpw== + dependencies: + follow-redirects "1.5.10" + babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -5127,6 +5141,14 @@ core_d@^1.0.1: dependencies: supports-color "^5.5.0" +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig@5.2.1, cosmiconfig@^5.0.0, cosmiconfig@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -5524,7 +5546,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@3.1.0: +debug@3.1.0, debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -6881,6 +6903,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -10975,7 +11004,7 @@ oauth@0.9.x: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= -object-assign@4.1.1, object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@4.1.1, object-assign@4.x, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -15181,7 +15210,7 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= From 02ffa1904ec95798e368169f3ff99b476d674942 Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:21:34 -0600 Subject: [PATCH 10/13] add avatar upload to settings page --- @app/client/src/pages/settings/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/@app/client/src/pages/settings/index.tsx b/@app/client/src/pages/settings/index.tsx index d32793f7..3c9f584f 100644 --- a/@app/client/src/pages/settings/index.tsx +++ b/@app/client/src/pages/settings/index.tsx @@ -12,7 +12,7 @@ import { ApolloError } from "apollo-client"; import { FormComponentProps, ValidateFieldsOptions } from "antd/lib/form/Form"; import { getCodeFromError, extractError } from "../../errors"; import { formItemLayout, tailFormItemLayout } from "../../forms"; -import { Redirect, ErrorAlert, H3 } from "@app/components"; +import { Redirect, ErrorAlert, H3, AvatarUpload } from "@app/components"; const Settings_Profile: NextPage = () => { const [formError, setFormError] = useState(null); @@ -113,6 +113,7 @@ function ProfileSettingsForm({ return (

Edit Profile

+
{getFieldDecorator("name", { From 882ab585454e45b51c56005aa2549597ed77121e Mon Sep 17 00:00:00 2001 From: Makon Cline Date: Wed, 22 Jan 2020 19:30:28 -0600 Subject: [PATCH 11/13] add labels to error, success, and avatar form item --- @app/client/src/pages/settings/index.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/@app/client/src/pages/settings/index.tsx b/@app/client/src/pages/settings/index.tsx index 3c9f584f..321064f4 100644 --- a/@app/client/src/pages/settings/index.tsx +++ b/@app/client/src/pages/settings/index.tsx @@ -113,8 +113,14 @@ function ProfileSettingsForm({ return (

Edit Profile

- + + + {getFieldDecorator("name", { initialValue: user.name, @@ -138,7 +144,7 @@ function ProfileSettingsForm({ })()} {error ? ( - + ) : success ? ( - + ) : null} From 594364c62dd7e74816475f7d371450f9d271f345 Mon Sep 17 00:00:00 2001 From: Makon Date: Wed, 22 Jan 2020 19:59:36 -0600 Subject: [PATCH 12/13] replace readme with instructions --- README.md | 470 ++++++------------------------------------------------ 1 file changed, 50 insertions(+), 420 deletions(-) diff --git a/README.md b/README.md index d99d3dd1..7191afb4 100644 --- a/README.md +++ b/README.md @@ -1,439 +1,69 @@ -# Graphile Starter +# Graphile Starter - Avatar Photo Upload -## Purpose +To use -Graphile Starter is an opinionated quick-start project for full-stack -application development in React, Node.js, GraphQL and PostgreSQL. It includes -the foundations of a modern web application, with a full user registration -system, session management, optimised job queue, a significant amount of -pre-configured tooling, tests (both end-to-end and more granular) and much more. - -It is suitable for building projects both large and small, with a focus on -productivity. You might use it: - -- to go from conception to launch of a web app during a hack day -- as the foundation for client projects at your web agency -- to build your side-hustle without spending lots of time on boilerplate -- to build a SaaS project to help fund your open source work - -However you use it, the project can be deployed to many platforms, and can be -scaled to larger audiences both horizontally and vertically with very few -changes. - -**Not intended for beginners**: this project combines a lot of technologies -together to produce a highly productive development environment, and includes a -pre-built database schema for an advanced account system. If you're not already -familiar with a lot of these technologies, or haven't built a database-driven -project before, you may find that there's too many things to get your head -around at once. For beginners, we recommend you start with the -[PostGraphile schema design tutorial](https://www.graphile.org/postgraphile/postgresql-schema-design/). - -Please note that this software is not "complete," free of software defects, or -free of security issues — it is not a "finished" solution, but rather the seed -of a solution which you should review, customize, fix, and develop further. - -It is intended that you use a "point in time" version of this software ─ it is -not intended that you can merge updates to this software into your own -derivative in an automated fashion. - - - -## Crowd-funded open-source software - -**PLEASE DONATE.** - -Take this software and use it as the starting point to build your project. Go -make some money, and [give something back](https://graphile.org/sponsor/) to -support us building more tools and kits for the Node, GraphQL and PostgreSQL -ecosystems. We have made this project available under the simple and liberal MIT -license to give you to a huge amount of freedom in how you use it, but this -isn't possible without the help of our wonderful sponsors. - -We need more people to join our sponsors so we can continue to bring about -awesome projects like this. We'd love to spend more time on open source, -building tools that will save you and others even more time and money ─ please -sponsor our open source efforts: - -### [Click here to find out more about sponsors and sponsorship.](https://www.graphile.org/sponsor/) - -And please give some love to our featured sponsors 🤩: - - - - - -
Chad Furman
Chad Furman
Storyscript
Storyscript
Point72 Ventures
Point72 Ventures
- - - -## Table of contents - -- [Features](#features) -- [Variants](#variants) -- [Prerequisites](#prerequisites) -- [Getting Started](#getting-started) -- [Running](#running) -- [Making it yours](#making-it-yours) -- [Docker development](#docker-development-1) -- [Production build](#production-build-for-local-mode) -- [Deploying to Heroku](#deploying-to-heroku) -- [License](#mit-license) - -## Features - -Graphile Starter is a full-stack [GraphQL](https://graphql.org/learn/) and -[React](https://reactjs.org/) project, with server-side rendering (SSR) and -routing thanks to [Next.js](https://nextjs.org/). The backend is a beautiful -pairing of Node.js and PostgreSQL running on Express.js, enabled by -[PostGraphile](https://www.graphile.org/postgraphile/) in library mode. The -frontend uses the [AntD](https://ant.design/) design framework to accelerate -development. The entire stack is written in TypeScript, with autogenerated -GraphQL types and operations thanks to -[graphql-code-generator](https://github.com/dotansimha/graphql-code-generator). - -There are four tenets to Graphile Starter: - -- Speedy development -- Batteries included -- Type safety -- Best practices - -Graphile Starter is easy to start and everything is preconfigured as much as -possible. - -**Speedy development**: hot reloading, easy debugging, Graphile's -[idempotent migration system](https://github.com/graphile/migrate), -[job queue](/TECHNICAL_DECISIONS.md#job-queue) and server middleware ready to -use; not to mention deep integration with VSCode should you use that editor: -plugin recommendations, preconfigured settings, ESLint and Prettier integration -and debugging profiles - -**Batteries included**: full user system and OAuth, AntD design framework, jest -and [Cypress end-to-end](/TECHNICAL_DECISIONS.md#cypress-e2e-tests) testing, -security, email templating and transport, pre-configured linting and code -formatting, deployment instructions, and more - -**Type safety**: pre-configured type checking, strongly typed throughout with -TypeScript - -**Best practices**: React, GraphQL, PostGraphile, Node, jest and Cypress best -practices - -See [TECHNICAL_DECISIONS.md](TECHNICAL_DECISIONS.md) for a more detailed list of -features included and the technical decisions behind them. - -## Variants - -Since this is a highly opinionated starter; community members may have slightly -different opinions and may choose to maintain forks of this project that apply -their own opinions. A few of these are listed below; if you maintain a fork of -this project please make a note at the top of your own README, and add it to -this list: - -- [Nuxt.js variant](https://github.com/JoeSchr/graphile-starter--private) - - replaces Next.js for Vue users -- [Create React App variant](https://github.com/alexk111/graphile-starter-cra) - - replaces Next.js for apps without Server Side Rendering - -## Prerequisites - -You can either work with this project locally (directly on your machine) or use -a pre-configured Docker environment. We'll differentiate this in the README with -a table like this one: - -| Local mode | OR | Docker mode | -| ------------------------------- | :-: | ---------------------------------------- | -| _command for local development_ | or | _command for docker-compose development_ | - -**Be careful not to mix and match Docker-mode vs local-mode for development.** -You should make a choice and stick to it. (Developing locally but deploying with -`production.Docker` is absolutely fine.) - -**IMPORTANT**: If you choose the Docker mode, be sure to read -[docker/README.md](docker/README.md). - -For users of Visual Studio Code (VSCode), a `.vscode` folder is included with -editor settings and debugger settings provided, plus a list of recommended -extensions. Should you need it, there is also a `.devcontainer` folder which -enables you to use -[VSCode's remote containers](https://code.visualstudio.com/docs/remote/containers) -giving you a local-like development experience whilst still using docker -containers. - -### Local development - -Requires: - -- Node.js v10+ must be installed (v12 recommended) -- PostgreSQL v10+ server must be available -- `pg_dump` command must be available (or you can remove this functionality) -- VSCode is recommended, but any editor will do - -This software has been developed under Mac and Linux, and should work in a -`bash` environment. - -**Windows users**: making a project like Graphile Starter run smoothly on -Windows can be a challenge; `@JoeSchr` and `@hips` on the -[Graphile Discord](http://discord.gg/graphile) have been working in improving -this and they're pretty pleased with the result, but you may still get some -teething problems. PRs to fix Windows compatibility issues are welcome (please -keep them small!) Failing that, try the Docker mode :) - -### Docker development - -Requires: - -- [`docker`](https://docs.docker.com/install/) -- [`docker-compose`](https://docs.docker.com/compose/install/) -- Ensure you've allocated Docker **at least** 4GB of RAM; significantly more - recommended - - (Development only, production is much more efficient) - -Has been tested on Windows and Linux (Ubuntu 18.04LTS). - -## Getting started - -This project is designed to work with `yarn`. If you don't have `yarn` -installed, you can install it with `npm install -g yarn`. The Docker setup -already has `yarn` & `npm` installed and configured. - -To get started, please run: - -| Local mode | OR | Docker mode | -| ------------ | :-: | ------------------------------- | -| `yarn setup` | or | `export UID; yarn docker setup` | - -This command will lead you through the necessary steps, and create a `.env` file -for you containing your secrets. - -**NOTE:** `export UID` is really important on Linux Docker hosts, otherwise the -files and folders created by Docker will end up owned by root, which is -non-optimal. We recommend adding `export UID` to your `~/.profile` or -`~/.bashrc` or similar so you don't have to remember it. - -**Do not commit `.env` to version control!** - -## Running - -You can bring up the stack with: - -| Local mode | OR | Docker mode | -| ------------ | :-: | ------------------------------- | -| `yarn start` | or | `export UID; yarn docker start` | - -After a short period you should be able to load the application at -http://localhost:5678 - -This main command runs a number of tasks: - -- uses [`graphile-migrate`](https://github.com/graphile/migrate) to watch - the`migrations/current.sql` file for changes, and automatically runs it - against your database when it changes -- watches the TypeScript source code of the server, and compiles it from - `@app/*/src` to `@app/*/dist` so node/`graphile-worker`/etc. can run the - compiled code directly -- runs the node server (includes PostGraphile and Next.js middleware) -- runs `graphile-worker` to execute your tasks (e.g. sending emails) -- watches your GraphQL files and your PostGraphile schema for changes and - generates your TypeScript React hooks for you automatically, leading to - strongly typed code with minimal effort -- runs the `jest` tests in watch mode, automatically re-running as the database - or test files change - -**NOTE**: `docker-compose up server` also runs the PostgreSQL server that the -system connects to. - -You may also choose to develop locally, but use the PostgreSQL server via -`docker-compose up -d db`. - -Then for development you may need a console; you can open one with: - -| Local mode | OR | Docker mode | -| ---------- | :-: | ------------------------------ | -| `bash` | or | `export UID; yarn docker bash` | - -To shut everything down: - -| Local mode | OR | Docker mode | -| ---------- | :-: | ------------------------------ | -| Ctrl-c | or | `export UID; yarn docker down` | - -## Making it yours - -1. Download and extract a zip of - [the latest release from GitHub](https://github.com/graphile/starter/releases) -1. In that folder run: - - `git init` - - `git add .` - - `git commit -m "Graphile Starter base"` -1. Change the project name in `package.json` -1. Change the project settings in `@app/config/src/index.ts` -1. Replace the `README.md` file -1. Add your own copyright notices to the `LICENSE.md` file -1. Commit as you usually would -1. [Show your appreciation with sponsorship](https://www.graphile.org/sponsor/) - -## Docker development - -Be sure to read [docker/README.md](docker/README.md). - -## Building the production docker image - -To build the production image, use `docker build` as shown below. You should -supply the `ROOT_URL` build variable (which will be baked into the client code, -so cannot be changed as envvars); if you don't then the defaults will apply -(which likely will not be suitable). - -To build the worker, pass `TARGET="worker"` instead of the default -`TARGET="server"`. - -```sh -docker build \ - --file production.Dockerfile \ - --build-arg ROOT_URL="http://localhost:5678" \ - --build-arg TARGET="server" \ - . -``` - -When you run the image you must pass it the relevant environmental variables, -for example: - -```sh -docker run --rm -it --init -p 5678:5678 \ - -e GRAPHILE_LICENSE="$GRAPHILE_LICENSE" \ - -e SECRET="$SECRET" \ - -e JWT_SECRET="$JWT_SECRET" \ - -e DATABASE_VISITOR="$DATABASE_VISITOR" \ - -e DATABASE_URL="$DATABASE_URL" \ - -e AUTH_DATABASE_URL="$AUTH_DATABASE_URL" \ - -e GITHUB_KEY="$GITHUB_KEY" \ - -e GITHUB_SECRET="$GITHUB_SECRET" \ - docker-image-id-here -``` - -Currently if you miss required envvars weird things will happen; we don't -currently have environment validation (PRs welcome!). - -## Production build for local mode - -Use `yarn run build` to generate a production build of the project - -## Deploying to Heroku - -If you are using `graphile-migrate` make sure that you have executed -`graphile-migrate commit` to commit all your database changes, since we only run -committed migrations in production. - -Make sure you have customized `@app/config/src/index.ts`. - -Make sure everything is committed and pushed in git. - -Set up a database server; we recommend using Amazon RDS. - -Once your database server is running, you can use our `heroku-setup` script to -automate the setup process. This script does the following: - -- Creates the Heroku app -- Adds the redis extension to this Heroku app -- Creates the database in the database server -- Creates the relevant roles, generating random passwords for them -- Installs some common database extensions -- Sets the Heroku config variables -- Adds the Heroku app as a git remote named 'Heroku' -- Pushes the 'master' branch to Heroku to perform your initial build - -Copy `heroku-setup.template` to `heroku-setup`, then edit it and customize the -settings at the top. We also recommend reading through the script and -customizing it as you see fit - particularly if you are using additional -extensions that need installing. - -Now run the script: +Create an S3 bucket +in permissions -> block public access, uncheck +- Block all public access +- Block public access to buckets and objects granted through new public bucket or access point policies +- Block public and cross-account access to buckets and objects through any public bucket or access point policies +in permissions -> bucket policy, add +(This allows the public to get all the files in the bucket, so make sure thats what you want.) +Replace BUCKET_NAME with your own ``` -bash heroku-setup +{ + "Version": "2008-10-17", + "Statement": [ + { + "Sid": "AllowPublicRead", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::BUCKET_NAME/*" + } + ] +} ``` - -Hopefully all has gone well. If not, step through the remaining tasks in the -Heroku-setup script and fix each task as you go. We've designed the script so -that if your superuser credentials are wrong, or the Heroku app already exists, -you can just edit the settings and try again. All other errors will probably -need manual intervention. Verbosity is high so you can track exactly what -happened. - -The server should be up and running now (be sure to access it over HTTPS -otherwise you will not be able to run GraphQL queries), but it is not yet -capable of sending emails. To achieve this, you must configure an email -transport. We have preconfigured support for Amazon SES. Once SES is set up, -your domain is verified, and you've verified any emails you wish to send email -to (or have had your sending limits removed), make sure that the `fromEmail` in -`@app/config/src/index.ts` is correct, and then create an IAM role for your -PostGraphile server. Here's an IAM template for sending emails - this is the -only permission required for our IAM role currently, but you may wish to add -others later. - +Go to AWS IAM Managment Console +Add user +Programmatic access +next +Attach existing policies directly +Create Policy +Json +Paste the following policy +Replace BUCKET_NAME with your own ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", - "Action": "ses:SendRawEmail", - "Resource": "*" + "Action": [ + "s3:*" + ], + "Resource": [ + "arn:aws:s3:::BUCKET_NAME/*" + ] } ] } ``` +name the policy +create policy +return to IAM Managment Console +refresh and add the new policy to the new user +finish creating user +save the Access key ID and Secret access key -Generate an Access Key for this IAM role, and then tell Heroku the access key id -and secret: +add AWS S3 bucket config to .env ``` -heroku config:set AWS_ACCESS_KEY_ID="..." AWS_SECRET_ACCESS_KEY="..." -a $APP_NAME +BUCKET=XXX +AWSACCESSKEYID=XXX +AWSSECRETKEY=XXX +AWS_REGION=XXX ``` - -Now you can tell Heroku to run the worker process as well as the currently -running 'web' process: - -``` -heroku ps:scale worker=1 -a $APP_NAME -``` - -When you register an account on the server you should receive a verification -email containing a clickable link. When you click the link your email will be -verified and thanks to GraphQL subscriptions the previous tab should be updated -to reflect that your account is now verified. - -**Remember** the first account registered will be an admin account, so be sure -to register promptly. - -You can also configure your application for social login. This works the same as -in development except the callback URL will be different, something like -`https://MY_HEROKU_APP_NAME.herokuapp.com/auth/github/callback`. Set the GitHub -OAuth secrets on your Heroku app to trigger a restart and enable social login: - -``` -heroku config:set GITHUB_KEY="..." GITHUB_SECRET="..." -a $APP_NAME -``` - -## Cleanup - -To delete the Heroku app: - -``` -heroku apps:destroy -a $APP_NAME -``` - -To delete the database/roles (replace `dbname` with your database name): - -``` -drop database dbname; -drop role dbname_visitor; -drop role dbname_authenticator; -drop role dbname; -``` - -## MIT License - -This is open source software; you may use, modify and distribute it under the -terms of the MIT License, see -[GRAPHILE_STARTER_LICENSE.md](./GRAPHILE_STARTER_LICENSE.md). From 84e304f5c3e4244217f3a0e14e6f3badf15c0549 Mon Sep 17 00:00:00 2001 From: Makon Date: Mon, 27 Jan 2020 11:41:12 -0600 Subject: [PATCH 13/13] add @app/graphql --- @app/components/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/@app/components/package.json b/@app/components/package.json index f38657be..b91aae21 100644 --- a/@app/components/package.json +++ b/@app/components/package.json @@ -10,6 +10,7 @@ "apollo-client": "^2.6.8", "next": "^9.2.0", "react": "^16.9.0", - "tslib": "^1.10.0" + "tslib": "^1.10.0", + "@app/graphql": "0.0.0" } }