Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ run-cloud.sh
.editorconfig
docker-compose*
./.github
./.next
./.next
./__generated__
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DATABASE_CONNECTION_STRING=postgres://dev:[email protected]:5432/golinks
DATABASE_SCHEMA=public
NODE_ENV=production
AUTH0_ENABLED=false
LOGONAME=go.localhost
HOSTNAME=http://localhost:3000
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ yarn-error.log*
# graphql
*.graphql.d.ts
*.graphqls.d.ts
__generated__

# misc
run-cloud.sh
3 changes: 2 additions & 1 deletion .graphql-let.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ plugins:
- typescript
- typescript-operations
- typescript-react-apollo
respectGitIgnore: true
cacheDir: __generated__
respectGitIgnore: true
2 changes: 1 addition & 1 deletion components/LinkForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Formik, Form, Field, FormikHelpers } from 'formik';

const CreateLinkSchema = Yup.object().shape({
alias: Yup.string()
.matches(/^[a-z0-9]+$/i)
.matches(/^[a-z0-9//]+$/i)
.max(24, 'Too long for an alias.')
.required('An alias is required.'),
url: Yup.string()
Expand Down
51 changes: 24 additions & 27 deletions components/LinkTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,20 @@ import { LinkMetricUsageGraph } from '../LinkMetricUsageGraph';

interface Props {
data: GetAllLinksQuery;
isDeleteEnabled: boolean;
isEditEnabled: boolean;
onDelete: (linkId: string) => void | Promise<void>;
onShare: (linkUrl: string) => void | Promise<void>;
onAnalytics: (linkId: string) => void | Promise<void>;
isDeleteEnabled: boolean;
onEdit: (linkId: string) => void | Promise<void>;
onShare: (linkUrl: string) => void | Promise<void>;
onDelete: (linkId: string) => void | Promise<void>;
}

export const LinkTable: React.FC<Props> = ({
data,
isDeleteEnabled,
isEditEnabled,
onDelete,
isDeleteEnabled,
onEdit,
onShare,
onAnalytics,
onDelete,
}) => {
const links = data?.links;

Expand Down Expand Up @@ -84,12 +82,14 @@ export const LinkTable: React.FC<Props> = ({
<DropdownMenu
menu={
<>
<DropdownMenu.Item
disabled={!isEditEnabled}
iconBefore="solid-edit"
onClick={() => onEdit(link.id)}>
Edit
</DropdownMenu.Item>
{isEditEnabled && (
<DropdownMenu.Item
disabled={!isEditEnabled}
iconBefore="solid-edit"
onClick={() => onEdit(link.id)}>
Edit
</DropdownMenu.Item>
)}
<DropdownMenu.Item
iconBefore="solid-share"
onClick={() => {
Expand All @@ -101,20 +101,17 @@ export const LinkTable: React.FC<Props> = ({
}}>
Share
</DropdownMenu.Item>
<DropdownMenu.Item
iconBefore="solid-chart-bar"
onClick={() => onAnalytics(link.id)}>
Analytics
</DropdownMenu.Item>
<DropdownMenu.Item
disabled={!isDeleteEnabled}
iconBefore="solid-trash-alt"
color="danger"
onClick={() => {
onDelete(link.id);
}}>
Delete
</DropdownMenu.Item>
{isDeleteEnabled && (
<DropdownMenu.Item
disabled={!isDeleteEnabled}
iconBefore="solid-trash-alt"
color="danger"
onClick={() => {
onDelete(link.id);
}}>
Delete
</DropdownMenu.Item>
)}
</>
}>
<Button size="small">
Expand Down
34 changes: 34 additions & 0 deletions components/NotFoundAnimation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { useBreakpointValue } from 'bumbag';
import Lottie from 'react-lottie';
import animationData from './lottiefile.json';

const defaultOptions = {
loop: true,
autoplay: true,
animationData,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice',
},
};

export const NotFoundAnimation: React.FC = () => {
const width = useBreakpointValue({
default: 600,
mobile: 250,
});

const height = useBreakpointValue({
default: 350,
mobile: 150,
});

return (
<Lottie
isClickToPauseDisabled
options={defaultOptions}
height={height}
width={width}
/>
);
};
1 change: 1 addition & 0 deletions components/NotFoundAnimation/lottiefile.json

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions db/migrations/20201011103804_create-search-links-function.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- migrate:up
create function search_links(search text)
returns setof public.links as $$
select *
from public.links
where
url ilike ('%' || search || '%') or
alias ilike ('%' || search || '%')
$$ language sql stable;

-- migrate:down
drop function search_links;
80 changes: 48 additions & 32 deletions db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -56,93 +56,108 @@ SET default_tablespace = '';
SET default_table_access_method = heap;

--
-- Name: link_usage_metrics; Type: TABLE; Schema: public; Owner: -
-- Name: links; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.link_usage_metrics (
CREATE TABLE public.links (
id uuid DEFAULT public.uuid_generate_v4() NOT NULL,
link_id uuid NOT NULL,
accessed_at timestamp without time zone DEFAULT now() NOT NULL
alias character varying(50) NOT NULL,
url character varying(255) NOT NULL,
created_at timestamp without time zone DEFAULT now() NOT NULL
);

ALTER TABLE ONLY public.link_usage_metrics FORCE ROW LEVEL SECURITY;
ALTER TABLE ONLY public.links FORCE ROW LEVEL SECURITY;


--
-- Name: TABLE link_usage_metrics; Type: COMMENT; Schema: public; Owner: -
-- Name: TABLE links; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON TABLE public.link_usage_metrics IS 'A link usage metric posted by the application when a link is accessed.';
COMMENT ON TABLE public.links IS 'A link alias posted by an user.';


--
-- Name: COLUMN link_usage_metrics.id; Type: COMMENT; Schema: public; Owner: -
-- Name: COLUMN links.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_usage_metrics.id IS 'The id for this metric record.';
COMMENT ON COLUMN public.links.id IS 'The id for a link alias.';


--
-- Name: COLUMN link_usage_metrics.link_id; Type: COMMENT; Schema: public; Owner: -
-- Name: COLUMN links.alias; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_usage_metrics.link_id IS 'The id of the link being accessed.';
COMMENT ON COLUMN public.links.alias IS 'The alias for an url. It must be unique.';


--
-- Name: COLUMN link_usage_metrics.accessed_at; Type: COMMENT; Schema: public; Owner: -
-- Name: COLUMN links.url; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.link_usage_metrics.accessed_at IS 'The time this link was accessed.';
COMMENT ON COLUMN public.links.url IS 'The link alias url.';


--
-- Name: links; Type: TABLE; Schema: public; Owner: -
-- Name: COLUMN links.created_at; Type: COMMENT; Schema: public; Owner: -
--

CREATE TABLE public.links (
id uuid DEFAULT public.uuid_generate_v4() NOT NULL,
alias character varying(50) NOT NULL,
url character varying(255) NOT NULL,
created_at timestamp without time zone DEFAULT now() NOT NULL
);
COMMENT ON COLUMN public.links.created_at IS 'The time this link alias was created.';

ALTER TABLE ONLY public.links FORCE ROW LEVEL SECURITY;

--
-- Name: search_links(text); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION public.search_links(search text) RETURNS SETOF public.links
LANGUAGE sql STABLE
AS $$
select *
from public.links
where
url ilike ('%' || search || '%') or
alias ilike ('%' || search || '%')
$$;


--
-- Name: TABLE links; Type: COMMENT; Schema: public; Owner: -
-- Name: link_usage_metrics; Type: TABLE; Schema: public; Owner: -
--

COMMENT ON TABLE public.links IS 'A link alias posted by an user.';
CREATE TABLE public.link_usage_metrics (
id uuid DEFAULT public.uuid_generate_v4() NOT NULL,
link_id uuid NOT NULL,
accessed_at timestamp without time zone DEFAULT now() NOT NULL
);

ALTER TABLE ONLY public.link_usage_metrics FORCE ROW LEVEL SECURITY;


--
-- Name: COLUMN links.id; Type: COMMENT; Schema: public; Owner: -
-- Name: TABLE link_usage_metrics; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.links.id IS 'The id for a link alias.';
COMMENT ON TABLE public.link_usage_metrics IS 'A link usage metric posted by the application when a link is accessed.';


--
-- Name: COLUMN links.alias; Type: COMMENT; Schema: public; Owner: -
-- Name: COLUMN link_usage_metrics.id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.links.alias IS 'The alias for an url. It must be unique.';
COMMENT ON COLUMN public.link_usage_metrics.id IS 'The id for this metric record.';


--
-- Name: COLUMN links.url; Type: COMMENT; Schema: public; Owner: -
-- Name: COLUMN link_usage_metrics.link_id; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.links.url IS 'The link alias url.';
COMMENT ON COLUMN public.link_usage_metrics.link_id IS 'The id of the link being accessed.';


--
-- Name: COLUMN links.created_at; Type: COMMENT; Schema: public; Owner: -
-- Name: COLUMN link_usage_metrics.accessed_at; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.links.created_at IS 'The time this link alias was created.';
COMMENT ON COLUMN public.link_usage_metrics.accessed_at IS 'The time this link was accessed.';


--
Expand Down Expand Up @@ -273,4 +288,5 @@ INSERT INTO public.schema_migrations (version) VALUES
('20200927100746'),
('20200927101112'),
('20200927101300'),
('20200927232549');
('20200927232549'),
('20201011103804');
44 changes: 19 additions & 25 deletions lib/apollo.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { IncomingMessage, ServerResponse } from 'http';
import { useMemo } from 'react';
import { ApolloClient } from 'apollo-client';
import {
HttpLink,
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
} from 'apollo-cache-inmemory';
} from '@apollo/client';

let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;

Expand All @@ -13,39 +14,32 @@ export type ResolverContext = {
res?: ServerResponse;
};

function createIsomorphLink(context: ResolverContext = {}) {
function createIsomorphicLink() {
let uri: string;

if (typeof window === 'undefined') {
const { SchemaLink } = require('apollo-link-schema');
const { schema } = require('./schema');
return new SchemaLink({ schema, context });
const { Config } = require('./config');
uri = new URL('/api/graphql', Config.metadata.hostname).href;
} else {
const { HttpLink } = require('apollo-link-http');
return new HttpLink({
uri: '/api/graphql',
credentials: 'same-origin',
headers: {},
});
uri = '/api/graphql';
}

return new HttpLink({
uri,
credentials: 'same-origin',
});
}

function createApolloClient(context?: ResolverContext) {
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(context),
cache: new InMemoryCache({
/** @ts-ignore */
dataIdFromObject: (object) => object.nodeId || null,
}),
link: createIsomorphicLink(),
cache: new InMemoryCache(),
});
}

export function initializeApollo(
initialState: any = null,
// Pages with Next.js data fetching methods, like `getStaticProps`, can send
// a custom context which will be used by `SchemaLink` to server render pages
context?: ResolverContext
) {
const _apolloClient = apolloClient ?? createApolloClient(context);
export function initializeApollo(initialState: any = null) {
const _apolloClient = apolloClient ?? createApolloClient();

// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
Expand Down
Loading