Remove client code

This commit is contained in:
Amit Jakubowicz 2019-08-02 19:16:35 +02:00
parent 2e61bdf813
commit 335a7b21fc
100 changed files with 354 additions and 14836 deletions

View File

@ -1,3 +1,4 @@
node_modules
client/node_modules
server/node_modules
tests
k8s
@types

View File

@ -14,4 +14,3 @@
.gitignore
node_modules
#!include:.gitignore

8
.gitignore vendored
View File

@ -1,7 +1,5 @@
dist/
yarn-error.log
config.ts
node_modules
.idea
yarn-error.log
.DS_Store
lib
.env
lib/

View File

@ -1,13 +0,0 @@
configure-functions:
@cat server/src/config.tmpl.ts \
| sed 's&_EMAIL_DOMAIN_&'"${EMAIL_DOMAIN}"'&' \
| sed 's&_DOMAIN_&'"${DOMAIN}"'&' \
| sed 's&_MAILGUN_API_KEY_&'"${MAILGUN_API_KEY}"'&' \
| sed 's&_GCP_PROJECT_ID_&'"${GCP_PROJECT_ID}"'&' > server/src/config.ts
configure-functions-staging:
@echo "Will populate config.ts with runtime secrets"
@$(MAKE) DOMAIN=${DOMAIN_STAGING} \
MAILGUN_API_KEY=${MAILGUN_API_KEY_STAGING} \
GCP_PROJECT_ID=${GCP_PROJECT_ID_STAGING} \
EMAIL_DOMAIN=${EMAIL_DOMAIN_STAGING} \
configure-functions

View File

@ -1,11 +0,0 @@
{
"presets": [
"@babel/env",
"@babel/preset-typescript",
"@emotion/babel-preset-css-prop"
],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-object-rest-spread"
]
}

View File

@ -1,77 +0,0 @@
import * as React from "react"
import { Redirect, Route, Switch } from "react-router-dom"
import Calendar from "../Calendar/Calendar"
import CreateEvent from "../Event/CreateEvent"
import EditEvent from "../Event/EditEvent"
import { RouteComponentProps } from "react-router"
import Header from "./Header/Header"
import Login from "./Auth/Login"
import styled from "@emotion/styled"
import Footer from "./Footer"
import InitializeSession from "./Auth/InitializeSession"
import Signup from "./Auth/Signup"
import Signout from "./Auth/Signout"
import OccurrenceDetails from "./Occurrence/OccurrenceDetails"
import { Global, css } from "@emotion/core"
const App = () => (
<Root>
<Global
styles={css`
body {
margin: 0;
height: 100vh;
}
#app {
height: 100%;
}
`}
/>
<StyledHeader />
<Content>
<Switch>
<Route path="/create" component={CreateEvent} />
<Route
path="/event/:eventId/edit"
render={(routeProps: RouteComponentProps<{ eventId: string }>) => (
<EditEvent eventId={routeProps.match.params.eventId} />
)}
/>
<Route
path="/o/:sanitizedEventName/:occurrenceId"
component={OccurrenceDetails}
/>
<Route path="/init-session/:hash" component={InitializeSession} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<Route path="/logout" component={Signout} />
<Route path="/" component={Calendar} />
<Redirect to="/" />
</Switch>
</Content>
<StyledFooter />
</Root>
)
const Root = styled.div`
display: grid;
height: 100%;
grid-template-columns: 1fr;
grid-template-rows: [header] 48px [body] 1fr [footer] 32px;
`
const Content = styled.div`
grid-row: body;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
`
const StyledFooter = styled(Footer)`
grid-row: footer;
`
const StyledHeader = styled(Header)`
grid-row: header;
`
export default App

View File

@ -1,43 +0,0 @@
import * as React from "react"
import { RouteComponentProps, withRouter } from "react-router"
import styled from "@emotion/styled"
interface RouteParams {
hash: string
}
interface Props extends RouteComponentProps<{ hash: string }> {}
const InitializeSession = (props: Props) => {
const [responseCode, setResponseCode] = React.useState(0)
const [loading, setLoading] = React.useState(true)
React.useEffect(() => {
fetch(`/api/init-session`, {
method: "post",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({ hash: props.match.params.hash })
}).then(res => {
setResponseCode(res.status)
setLoading(false)
if (res.status === 200) {
props.history.replace("/my-events")
}
})
})
return (
<Root>
<p>Welcome back. Validating you link should take just a moment</p>
{loading
? "Loading..."
: responseCode === 200
? "You will be redirected now"
: "Validation failed. Please try to log in again."}
</Root>
)
}
const Root = styled.div``
export default withRouter<Props>(InitializeSession)

View File

@ -1,96 +0,0 @@
import * as React from "react"
import styled from "@emotion/styled"
const sendLogin = (email: string) => {
return fetch("/api/login", {
method: "post",
body: JSON.stringify({email}),
headers:{
'Content-Type': 'application/json'
}
})
}
const Login = () => {
const [loading, setLoading] = React.useState(false)
const [email, setEmail] = React.useState("")
const [success, setSuccess] = React.useState(false)
const isValid = /\w@\w.\w/.test(email)
const [error, setError] = React.useState(false)
return (
<Root onSubmit={(e) => {
e.preventDefault()
setError(false)
setLoading(true)
sendLogin(email)
.then((res) => {
if (res.status === 200) {
setSuccess(true)
} else {
setError(true)
}
})
.catch(() => {
setError(true)
})
}}>
{
error ? (
<p>
Could not find a user with the mentioned email. Please sign up first.
</p>
) : (
success ? (
<>
<p>Invitation was sent to your email: {email}</p>
<p>Please check your email and click on the link provided in the invitation</p>
</>
) : (
<>
<label htmlFor="email">Please enter your email to log in</label>
<Input
id="email"
type="email"
value={email}
onChange={e => {
setEmail(e.target.value)
}}
disabled={loading || success}
/>
<Button
disabled={!isValid || success}
>
Login
</Button>
</>
)
)
}
</Root>
)
}
const Root = styled.form`
display: flex;
font-size: 24px;
color: rgba(0, 0, 0, 0.6);
flex-direction: column;
width: 320px;
`
const Input = styled.input`
height: 38px;
font-size: 24px;
text-align: center;
color: rgba(0, 0, 0, 0.7);
margin-top: 18px;
`
const Button = styled.button`
height: 38px;
width: 68px;
align-self: center;
margin-top: 48px;
`
export default Login

View File

@ -1,21 +0,0 @@
import * as React from 'react'
import {RouteComponentProps, withRouter} from "react-router"
interface Props extends RouteComponentProps {
}
const Signout = (props: Props) => {
React.useEffect(() => {
fetch('/api/signout')
.then(() => {
props.history.push('/')
}).finally(() => {
props.history.push('/')
})
})
return <p>You will be logged out now</p>
}
export default withRouter(Signout)

View File

@ -1,64 +0,0 @@
import * as React from "react"
import styled from "@emotion/styled"
import {FormEvent} from "react"
const Signup = () => {
const [loading, setLoading] = React.useState(false)
const [success, setSuccess] = React.useState(null)
const [error, setError] = React.useState(null)
const [emailTaken, setEmailTaken] = React.useState(false)
return (
<form
onSubmit={(e: FormEvent) => {
e.preventDefault()
const form = e.target as any
fetch("/api/signup", {
method: "post",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
name: form.name.value,
email: form.email.value
})
}).then((res) => {
if (res.status === 200) {
setSuccess(true)
return
} else {
setError(true)
setSuccess(false)
}
if (res.status === 409) {
setEmailTaken(true)
}
}).catch(() => {
setError(true)
})
}}
>
<Input name="name" placeholder="Your name"/>
<Input name="username" placeholder="Choose a username"/>
<Input name="email" type="email" placeholder="Your email"/>
<Button type="submit">Sign Up</Button>
{
emailTaken ? (
<p>This email is already registered. Please log in.</p>
) : null
}
{
error ? <p>We have encountered an error. Please try again.</p> : null
}
</form>
)
}
const Root = styled.form``
const Input = styled.input``
const Button = styled.button``
export default Signup

View File

@ -1,24 +0,0 @@
import * as React from "react"
import MeQuery, { UserData } from "./MeQuery"
interface IAppContext {
me: UserData
}
const { Provider, Consumer } = React.createContext<IAppContext>({ me: null })
const AppContextProvider = props => (
<MeQuery>
{({ data, loading, error }) => {
return (
<Provider value={data}>
{
props.children
}
</Provider>
)
}}
</MeQuery>
)
export { AppContextProvider, Consumer as AppContext }

View File

@ -1,34 +0,0 @@
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
const query = gql`
query Me {
me {
id
username
events {
id
}
}
}
`
export interface UserEventData {
id: string
}
export interface UserData {
id: string
username: string
events: UserEventData[]
}
interface Data {
me: UserData
}
export default class MeQuery extends Query<Data> {
static defaultProps = {
query
}
}

View File

@ -1,17 +0,0 @@
import * as React from 'react'
import styled from '@emotion/styled'
interface Props {
className?: string
}
const Footer = (props: Props) => (
<Root className={props.className}>
</Root>
)
const Root = styled.div`
background: rgba(0, 0, 0, 0.1);
`
export default Footer

View File

@ -1,55 +0,0 @@
import { AppContext } from "../Context/AppContext"
import { Link } from "react-router-dom"
import * as React from "react"
import styled from "@emotion/styled"
interface Props {
className?: string
}
const Header = (props: Props) => (
<AppContext>
{({ me }) => (
<Root className={props.className}>
<Menu />
<Title />
<LinksSection>
{me ? (
<>
<StyledLink to="/create">Create event</StyledLink>
<StyledLink to="/logout">Log out</StyledLink>
</>
) : (
<>
<StyledLink to="/login">Log In</StyledLink>
<StyledLink to="/signup">Sign Up</StyledLink>
</>
)}
</LinksSection>
</Root>
)}
</AppContext>
)
const StyledLink = styled(Link)`
color: rgba(0,0,0,.7);
&:not(:first-of-type) {
margin-left: 8px;
}
`
const Menu = styled.div``
const Title = styled.div`
flex: 1;
`
const Root = styled.div`
background: rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: row;
`
const LinksSection = styled.div`
align-self: center;
padding: 4px;
`
export default Header

View File

@ -1,2 +0,0 @@
import Header from './Header'
export default Header

View File

@ -1,53 +0,0 @@
import * as React from 'react'
import {RouteComponentProps, withRouter} from "react-router"
import OccurrenceDetailsQuery from "./OccurrenceDetailsQuery"
import styled from '@emotion/styled'
interface RouteParams {
occurrenceId: string
sanitizedEventName: string
}
interface Props extends RouteComponentProps<RouteParams> {
}
const OccurrenceDetails = (props: Props) => {
return <OccurrenceDetailsQuery variables={{occurrenceId: props.match.params.occurrenceId}}>
{
({data, loading, error}) => {
if (loading) {
return <p>loading...</p>
}
if (error) {
return <p>{error.message}</p>
}
const info = data.occurrence.event.info[0]
return (
<Root>
<Title>
{ info.title }
</Title>
<Info>
{ info.description }
</Info>
</Root>
)
} }
</OccurrenceDetailsQuery>
}
const Title = styled.div`
grid-row: title;
`
const Info = styled.div`
grid-row: info
`
const Root = styled.div`
display: grid;
grid-template-rows: 48px [title] 1fr [info];
`
export default withRouter(OccurrenceDetails)

View File

@ -1,53 +0,0 @@
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
const query = gql`
query GetOccurrenceDetails($occurrenceId: ID!) {
occurrence(id: $occurrenceId) {
id
start
end
event {
id
owner {
id
name
}
info {
description
language
title
}
}
}
}
`
export interface OccurrenceDetailsData {
occurrence: {
id: string
start: string
end: string
event: {
owner: {
id: string
name: string
}
info: Array<{
description
language
title
}>
}
}
}
interface Variables {
occurrenceId: string
}
export default class OccurrenceDetailsQuery extends Query<OccurrenceDetailsData, Variables> {
static defaultProps = {
query
}
}

View File

@ -1,36 +0,0 @@
import * as React from 'react'
import {AppContextProvider} from "./Context/AppContext"
import {BrowserRouter as Router} from "react-router-dom"
import {ApolloProvider} from "react-apollo"
import {HttpLink} from "apollo-link-http"
import {ApolloClient} from "apollo-client"
import {InMemoryCache} from "apollo-cache-inmemory"
import fetch from 'node-fetch'
interface Props {
children: React.ReactChild | React.ReactChildren
}
const httpLink = new HttpLink({
uri: "/graphql",
fetch
})
const graphqlClient = new ApolloClient({
connectToDevTools: true,
link: httpLink,
cache: new InMemoryCache().restore(window.__APOLLO_DATA__)
}) as ApolloClient<any>
const Providers = (props: Props) => (
<ApolloProvider client={graphqlClient}>
<AppContextProvider>
<Router>
{ props.children }
</Router>
</AppContextProvider>
</ApolloProvider>
)
export default Providers

View File

@ -1,49 +0,0 @@
import removeTypename from '../remove-typename'
describe('Remove Typename Util', () => {
it('remove nested typename', () => {
const input = {
"input": {
"id": "4b18628f-6069-4d6f-9708-f85862b2c3e6",
"meta": {"tags": []},
"time": {
"start": "2019-06-12T10:00",
"end": "2019-06-12T12:00",
"timeZone": "Europe/Madrid",
"recurrence": null
},
"location": {"address": "housy str...", "name": "my house", "__typename": "Location"},
"info": [{
"description": "Party in my house (:",
"language": "en",
"title": "My Event",
"__typename": "EventInformation"
}],
"status": "confirmed"
}
}
const expected = {
"input": {
"id": "4b18628f-6069-4d6f-9708-f85862b2c3e6",
"meta": {"tags": []},
"time": {
"start": "2019-06-12T10:00",
"end": "2019-06-12T12:00",
"timeZone": "Europe/Madrid",
"recurrence": null
},
"location": {"address": "housy str...", "name": "my house"},
"info": [{
"description": "Party in my house (:",
"language": "en",
"title": "My Event",
}],
"status": "confirmed"
}
}
expect(removeTypename(input)).toEqual(expected)
})
})

View File

@ -1,12 +0,0 @@
import * as React from "react"
import * as ReactDOM from "react-dom"
import App from "./App"
import Providers from "./Providers"
const container = document.getElementById("app")
ReactDOM.render(
<Providers>
<App />
</Providers>,
container
)

View File

@ -1,11 +0,0 @@
import { clone } from 'ramda'
const omitTypename = (key, value) => {
return key === '__typename' ? undefined : value
}
const removeTypename = (object: any) => {
return JSON.parse(JSON.stringify(object), omitTypename)
}
export default removeTypename

View File

@ -1,53 +0,0 @@
import * as React from "react"
import OccurrencesQuery from "../Event/OccurrencesQuery"
import List from "./List"
interface Props {
}
const beginningOfThisMonth = (() => {
const dt = new Date()
dt.setDate(1)
return dt
}
)()
const endOfThisMonth = (
() => {
const dt = new Date()
dt.setMonth(dt.getMonth() + 1)
dt.setDate(0)
return dt
}
)()
const Calendar = (props: Props) => {
return (
<OccurrencesQuery
variables={{
filter: {
from: "2019-06-01",
to: "2019-06-30"
}
}}
>
{({data, error, loading}) => {
if (loading) {
return "I am a Spinner"
}
if (error) {
return error.message
}
if (!data.occurrences.length) {
return <p>No occurrences</p>
}
return <List occurrences={data.occurrences}/>
}}
</OccurrencesQuery>
)
}
export default Calendar

View File

@ -1,20 +0,0 @@
import * as React from 'react'
import {OccurrenceData} from "../Event/OccurrencesQuery"
interface Props {
occurrence: OccurrenceData
language?: string
}
const CalendarOccurrence = (props: Props) => {
const event = props.occurrence.event
const localInfo = props.language
? event.info.find(info => info.language === props.language)
: event.info[0]
return (<div>
{event.info[0].title}
</div>)
}
export default CalendarOccurrence

View File

@ -1,44 +0,0 @@
import * as React from "react"
import { OccurrenceData } from "../../Event/OccurrencesQuery"
import ListItem from "./ListItem"
interface Props {
occurrences: OccurrenceData[]
}
const List = (props: Props) => {
const sorted = [...props.occurrences]
sorted.sort((occA, occB) => {
if (occA.start > occB.start) {
return 1
}
if (occA.start < occB.start) {
return -1
}
return 0
})
const days: { [day: string]: OccurrenceData[] } = {}
sorted.forEach(occ => {
const day = occ.start.substring(0, 10)
if (!days[day]) {
days[day] = []
}
days[day].push(occ)
})
const dayNames = Object.keys(days)
return (
<div>
<h3>Events for the dates between {dayNames[0]} and {dayNames[dayNames.length-1]}</h3>
{dayNames.map(dayName => (
<ul key={dayName}>
<li>{dayName}</li>
{days[dayName].map(occ => (
<ListItem key={occ.id} occurrence={occ} />
))}
</ul>
))}
</div>
)
}
export default List

View File

@ -1,46 +0,0 @@
import * as React from "react"
import { OccurrenceData } from "../../Event/OccurrencesQuery"
import { AppContext } from "../../App/Context/AppContext"
import { Link } from 'react-router-dom'
import styled from '@emotion/styled'
interface Props {
occurrence: OccurrenceData
}
const sanitizeEventName = (name: string) => {
return encodeURIComponent(name.trim().toLocaleLowerCase()
.replace(/\s+/g,'-'))
}
const ListItem = (props: Props) => {
const { occurrence } = props
const { event } = occurrence
const info = event.info[0]
const startTime = occurrence.start.split(" ")[1].substring(0, 5)
return (
<AppContext>
{({ me }) => (
<div>
{startTime}
&nbsp;
<Link to={`/o/${sanitizeEventName(event.info[0].title)}/${occurrence.id}`}>
{info.title}
</Link>
{
me && me.events.find(myEvent => myEvent.id === event.id) ? (
<EditLink to={`/event/${event.id}/edit`}>Edit</EditLink>
) : null
}
</div>
)}
</AppContext>
)
}
const EditLink = styled(Link)`
margin-left: 8px;
font-size: 0.6em;
`
export default ListItem

View File

@ -1,2 +0,0 @@
import List from './List'
export default List

View File

@ -1,31 +0,0 @@
import * as React from 'react'
import CreateEventMutation from './CreateEventMutation'
import EventForm, {EventFormData} from "./EventForm"
const CreateEvent = () => {
return <CreateEventMutation>
{
(createEvent, { loading }) => (
<EventForm
loading={loading}
onSubmit={(values: EventFormData) => {
createEvent({
variables: {
input: {
info: values.info,
location: values.location,
time: {
...values.time,
timeZone: 'Europe/Madrid',
},
status: 'confirmed',
meta: values.meta
}
}
})
}} />
)}
</CreateEventMutation>
}
export default CreateEvent

View File

@ -1,57 +0,0 @@
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
import {GQL} from "../../@types"
const mutation = gql`
mutation CreateEvent($input: CreateEventInput!) {
createEvent(input: $input) {
id
info {
description
language
title
}
location {
address
name
}
occurrences {
id
start
end
}
}
}
`
interface Variables {
input: GQL.ICreateEventInput
}
interface InfoData {
desciption: string
language: string
title: string
}
interface OccurrenceData {
id: string
start: string
end: string
}
interface Data {
id: string
info: InfoData[]
location: {
address: string
name: string
}
occurrences: OccurrenceData[]
}
export default class CreateEventMutation extends Mutation<Data, Variables> {
static defaultProps = {
mutation
}
}

View File

@ -1,24 +0,0 @@
import * as React from "react"
interface Props {
value: string
onChange: (value: string) => void
}
const DateTime = ({ value, onChange }: Props) => {
const dateTimeSplit = value.split('T')
return (
<div>
<input type="date" value={dateTimeSplit[0]} onChange={(e) => {
const newDateValue = e.target.value
onChange([newDateValue, dateTimeSplit[1]].join('T'))
}}/>
<input type="time" value={dateTimeSplit[1]} onChange={(e) => {
const newTimeValue = e.target.value
onChange([dateTimeSplit[0], newTimeValue].join('T'))
}}/>
</div>
)
}
export default DateTime

View File

@ -1,56 +0,0 @@
import * as React from 'react'
import GetEventQuery from "./GetEventQuery"
import EventForm from "./EventForm"
import EditEventMutation from "./EditEventMutation"
import removeTypename from "../App/remove-typename";
interface Props {
eventId: string
}
const EditEvent = (props: Props) => (
<EditEventMutation>
{
(editEvent, { loading: editLoading }) => (
<GetEventQuery skip={!props.eventId} variables={{id: props.eventId}}>
{
({data, error, loading}) => {
if (loading) {
return 'Loading...'
}
if (error) {
return error.message
}
const event = removeTypename(data.event)
return (
<EventForm
loading={loading}
onSubmit={values => {
editEvent({
variables: {
input: {
id: props.eventId,
...values
}
}
})
}}
values={{
meta: {
tags: event.meta.tags
},
time: event.time,
location: event.location,
info: event.info,
status: event.status
}}/>
)
}
}
</GetEventQuery>
)
}
</EditEventMutation>
)
export default EditEvent

View File

@ -1,27 +0,0 @@
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
import {EventData, EventFragment} from "./GetEventQuery"
import {GQL} from "../../@types"
const mutation = gql`
${EventFragment}
mutation EditEvent($input: UpdateEventInput!) {
updateEvent(input: $input) {
...EventData
}
}
`
interface Data {
updateEvent: EventData
}
interface Variables {
input: GQL.IUpdateEventInput
}
export default class EditEventMutation extends Mutation<Data, Variables> {
static defaultProps = {
mutation
}
}

View File

@ -1,2 +0,0 @@
import gql from 'graphql-tag'

View File

@ -1,30 +0,0 @@
import * as React from "react"
import { EventData } from "./EventsQuery"
import {OccurrenceData} from "./OccurrencesQuery";
interface Props {
event: EventData
language?: string
}
const Event = (props: Props) => {
// Get desired language or fallback to first language
const localInfo = props.language
? props.event.info.find(info => info.language === props.language)
: props.event.info[0]
return (
<div>
{
localInfo ? (
<React.Fragment>
<h1> {localInfo.title} </h1>
<p> {localInfo.description} </p>
</React.Fragment>
) : 'Info not available'
}
</div>
)
}
export default Event

View File

@ -1,115 +0,0 @@
import * as React from "react"
import { Formik, Form, Field } from "formik"
import styled from "styled-components"
import { EventStatus } from "../../@types"
import DateTime from "./DateTime";
interface Props {
values?: EventFormData
onSubmit: (values: EventFormData) => void
loading: boolean
}
export interface EventFormData {
time: {
timeZone: string;
start: string;
end: string;
recurrence?: string;
}
info: Array<{
language: string;
title: string;
description: string;
}>
location: {
address?: string;
name: string;
}
status: EventStatus
meta: {
tags: string[];
}
}
class EventFormik extends Formik<EventFormData> {}
const nextWeekTenAM = new Date()
nextWeekTenAM.setUTCDate(nextWeekTenAM.getDate() + 7)
nextWeekTenAM.setUTCHours(10, 0)
const nextWeekMidday = new Date(nextWeekTenAM)
nextWeekMidday.setUTCHours(12)
const EventForm = (props: Props) => {
const isEdit = !!props.values
return (
<EventFormik
onSubmit={props.onSubmit}
initialValues={
props.values
? props.values
: {
time: {
timeZone: "Europe/Madrid",
start: nextWeekTenAM.toISOString().substring(0, 16),
end: nextWeekMidday.toISOString().substring(0, 16)
},
info: [
{
language: "en",
title: "",
description: ""
}
],
location: {
name: ""
},
meta: {
tags: []
},
status: "confirmed"
}
}
validate={values => {
const errors: any = {}
}}
>
{({ isValid, setFieldValue }) => (
<StyledForm>
<Field name="info[0].title">
{({ field }) => <input {...field} placeholder="Name your event" />}
</Field>
<Field name="info[0].description">
{({ field }) => (
<textarea
{...field}
placeholder="Write a few words about your event"
/>
)}
</Field>
<Field name="time.start">
{
({ field }) => <DateTime {...field} onChange={(newStartValue) => setFieldValue('time.start', newStartValue)} />
}
</Field>
<Field name="time.end">
{
({ field }) => <DateTime {...field} onChange={(newEndValue) => setFieldValue('time.end', newEndValue)} />
}
</Field>
<Field name="location.name">
{({ field }) => <input {...field} placeholder="Location's name" />}
</Field>
<Field name="location.address">
{({ field }) => <input {...field} placeholder="Address" />}
</Field>
<button type="submit">{isEdit ? "Edit" : "Create"}</button>
</StyledForm>
)}
</EventFormik>
)
}
const StyledForm = styled(Form)``
export default EventForm

View File

@ -1,46 +0,0 @@
import {Query} from 'react-apollo'
import gql from 'graphql-tag'
const query = gql`
query EventsQuery($filter: EventsQueryFilter!) {
events(filter: $filter) {
id
status
info {
title
description
language
}
}
}
`
interface Variables {
filter: {
limit?: number
owner?: number
from?: string
to?: string
categories?: string[]
}
}
export interface EventData {
id: string
status: string
info: Array<{
title: string
description: string
language: string
}>
}
interface Data {
events: EventData[]
}
export default class EventsQuery extends Query<Data, Variables> {
static defaultProps = {
query
}
}

View File

@ -1,88 +0,0 @@
import {Query} from 'react-apollo'
import gql from 'graphql-tag'
import {EventStatus} from "../../@types"
export const EventFragment = gql`
fragment EventData on CalendarEvent {
id
info {
description
language
title
}
location {
address
name
}
time {
start
end
timeZone
recurrence
}
status
meta {
tags
}
}
`
export interface EventTimeData {
start: string
end: string
timeZone: string
recurrence: string
}
export interface EventInfoData {
description: string
language: string
title: string
}
export interface ContactData {
contact: {
email: string
phone: string
}
languages: string[]
name: string
}
export interface EventMetaData {
tags: string[]
}
export interface EventData {
id: string
info: EventInfoData[]
contact: ContactData[]
location: {
address: string
name: string
}
time: EventTimeData
status: EventStatus
meta: EventMetaData
}
interface Data {
event: EventData
}
const query = gql`
${EventFragment}
query GetEvent($id: ID!) {
event(id: $id) {
...EventData
}
}
`
interface Variables {
id: string
}
export default class GetEventQuery extends Query<Data, Variables> {
static defaultProps = { query }
}

View File

@ -1,49 +0,0 @@
import {Query} from 'react-apollo'
import gql from 'graphql-tag'
import {GQL} from "../../@types"
const query = gql`
query EventsQuery($filter: OccurrencesQueryFilter!) {
occurrences(filter: $filter) {
id
start
end
event {
id
info {
language
title
}
}
}
}
`
interface Variables {
filter: GQL.IOccurrencesQueryFilter
}
export interface InfoData {
language: string
title: string
}
export interface OccurrenceData {
id: string
start: string
end: string
event: {
id: string
info: InfoData[]
}
}
interface Data {
occurrences: OccurrenceData[]
}
export default class OccurrencesQuery extends Query<Data, Variables> {
static defaultProps = {
query
}
}

View File

@ -1,25 +0,0 @@
import * as React from 'react'
import {AppContextProvider} from "../App/Context/AppContext"
import {ApolloProvider} from "react-apollo"
import {ApolloClient} from "apollo-client"
import {InMemoryCache} from "apollo-cache-inmemory"
import { StaticRouter } from 'react-router'
interface Props {
children: React.ReactChild | React.ReactChildren
graphqlClient: ApolloClient<InMemoryCache>
location: string
}
const SSRProviders = (props: Props) => (
<ApolloProvider client={props.graphqlClient}>
<AppContextProvider>
<StaticRouter location={props.location}>
{ props.children }
</StaticRouter>
</AppContextProvider>
</ApolloProvider>
)
export default SSRProviders

View File

@ -1,57 +0,0 @@
import { Request, Response } from "express-serve-static-core"
import * as ReactDOMServer from "react-dom/server"
import { renderStylesToString } from "emotion-server"
import App from "../App/App"
import * as React from "react"
import SSRProviders from "./SSRProviders"
import * as fs from "fs"
import * as path from "path"
import * as Mustache from "mustache"
import { ApolloClient } from "apollo-client"
import { InMemoryCache } from "apollo-cache-inmemory"
import { HttpLink } from "apollo-link-http"
import fetch from "node-fetch"
import { getDataFromTree } from "react-apollo"
import apolloLogger from "apollo-link-logger"
import { ApolloLink } from "apollo-link"
export const httpSSRHandler = async (req: Request, res: Response) => {
res.status(200)
const httpLink = new HttpLink({
uri: "http://localhost:4000/graphql",
fetch,
headers: {
cookie: req.header("Cookie")
}
})
const link = ApolloLink.from([apolloLogger, httpLink])
const graphqlClient = new ApolloClient({
connectToDevTools: true,
link,
cache: new InMemoryCache(),
ssrMode: true,
}) as ApolloClient<any>
const app = (
<SSRProviders location={req.path} graphqlClient={graphqlClient}>
<App />
</SSRProviders>
)
const appWithData = await getDataFromTree(app)
const appBody = renderStylesToString(appWithData)
const initialState = graphqlClient.extract()
const template = fs.readFileSync(
path.join(__dirname, "./index.html.mustache"),
"utf-8"
)
const result = Mustache.render(template, {
appBody,
apolloData: JSON.stringify(initialState)
})
res.send(result)
}

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Quepasa Alpujarra</title>
<script type="application/javascript">
__APOLLO_DATA__ = {{{ apolloData }}};
</script>
</head>
<body>
<div id="app">{{{ appBody }}}</div>
<script type="application/javascript" src="/bundle.js"></script>
</body>
</html>

View File

@ -1,9 +0,0 @@
import * as express from 'express'
import { httpSSRHandler } from './handler'
const port = 5000
console.log(`Starting SSR on port ${port}...`)
const app = express()
app.get('/*', httpSSRHandler)
app.listen(port)
console.log('Successfully started')

View File

@ -1,79 +0,0 @@
{
"name": "client",
"version": "0.0.1",
"main": "index.ts",
"license": "private",
"private": true,
"scripts": {
"build": "webpack --config ./webpack.config.ts",
"start": "NODE_ENV=development webpack-dev-server --config ./webpack.config.ts --hot --progress",
"ssr": "ts-node SSR/index.tsx"
},
"dependencies": {
"@emotion/styled": "^10.0.12",
"@types/mustache": "^0.8.32",
"@types/react-router-dom": "^4.3.3",
"apollo-cache-inmemory": "^1.3.8",
"apollo-client": "^2.4.5",
"apollo-link": "^1.2.12",
"apollo-link-http": "^1.5.14",
"apollo-link-logger": "^1.2.3",
"date-fns": "^1.29.0",
"emotion-server": "^10.0.14",
"express": "^4.17.1",
"formik": "^1.4.1",
"graphql": "^14.0.2",
"graphql-tag": "^2.9.2",
"graphql-tools": "^4.0.3",
"jest": "^24.8.0",
"jest-cli": "^24.8.0",
"keycode": "^2.2.0",
"mustache": "^3.0.1",
"node-fetch": "^2.6.0",
"nodemon": "^1.19.1",
"ramda": "^0.26.1",
"react": "^16.8.6",
"react-apollo": "^2.2.4",
"react-dom": "^16.8.6",
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
"styled-components": "^4.1.3"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.3.3",
"@babel/plugin-proposal-class-properties": "^7.3.3",
"@babel/plugin-proposal-object-rest-spread": "^7.3.2",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-export-default-from": "^7.2.0",
"@babel/plugin-syntax-jsx": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@emotion/babel-preset-css-prop": "^10.0.9",
"@types/jest": "^24.0.6",
"@types/node": "^11.9.4",
"@types/react": "^16.4.18",
"@types/react-dom": "^16.8.4",
"@types/react-loadable": "^5.4.1",
"@types/react-router": "^4.0.31",
"@types/styled-components": "^4.1.10",
"@types/webpack": "^4.4.27",
"@types/webpack-dev-server": "^3.1.2",
"apollo-link-context": "^1.0.14",
"babel-loader": "^8.0.5",
"babel-plugin-styled-components": "^1.10.0",
"html-webpack-plugin": "^3.2.0",
"react-hot-loader": "^4.8.2",
"ts-jest": "^24.0.0",
"ts-node": "^8.0.2",
"tslint": "^5.12.1",
"tslint-config-prettier": "^1.16.0",
"typescript": "^3.5.3",
"webpack": "^4.30.0",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.3.1",
"webpack-html-plugin": "^0.1.1",
"yarn": "^1.13.0"
}
}

View File

@ -1,16 +0,0 @@
{
"compilerOptions": {
"skipLibCheck": true,
"sourceMap": true,
"jsx": "react",
"module": "commonjs",
"lib": [
"dom",
"es7",
"esnext.asynciterable"
]
},
"exclude": [
".*/__tests__/.*", "node_modules"
]
}

View File

@ -1,63 +0,0 @@
import * as webpack from 'webpack'
import * as path from "path"
import * as HtmlWebpackPlugin from 'html-webpack-plugin'
import * as express from 'express'
import * as WebpackDevServer from "webpack-dev-server"
import {httpSSRHandler} from "./SSR/handler"
const config: webpack.Configuration = {
entry: './App/index.tsx',
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"]
},
devServer: {
historyApiFallback: true,
hot: true,
before: (app: express.Application, server: WebpackDevServer) => {
//todo: improve the regex
app.get(/^((?!\.\w+).)*$/, httpSSRHandler)
},
proxy: {
'/graphql': {
redirect: false,
changeOrigin: true,
target: `http://localhost:4000`,
},
'/api': {
redirect: false,
changeOrigin: true,
target: `http://localhost:4000`,
}
}
},
module: {
rules: [
{
exclude: path.resolve(__dirname, "node_modules"),
test: /\.tsx?$/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/typescript",
"@babel/react"
],
plugins: [].filter(Boolean)
}
}
} ]
},
devtool: '@source-map',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js',
publicPath: '/'
},
plugins: [new HtmlWebpackPlugin({
title: "blabla",
})]
}
export default config

File diff suppressed because it is too large Load Diff

View File

@ -48,7 +48,7 @@ spec:
spec:
containers:
- name: master
image: eu.gcr.io/qpa-staging-237606/qpa:0.0.4
image: eu.gcr.io/qpa-staging-237606/qpa:0.0.6
resources:
requests:
cpu: 100m
@ -56,6 +56,8 @@ spec:
ports:
- containerPort: 4000
env:
- name: DOMAIN
value: "alpha.quapasaalpujarra.com"
- name: DB_USER
value: "qpa"
- name: POSTGRES_DB
@ -64,3 +66,9 @@ spec:
value: "1zrBGfidoPzf"
- name: POSTGRES_HOST
value: "10.79.112.3"
- name: MAILGUN_KEY
value: "key-59ad615180cb44e4ff5fd41236a9694a"
- name: EMAIL_DOMAIN
value: "staging.quepasaalpujarra.com"

View File

@ -1,30 +1,62 @@
{
"name": "gcf-auth",
"private": true,
"version": "1.0.0",
"main": "index.js",
"repository": "git@gitlab.com:amiiit/gcf-auth.git",
"author": "Amit Jakubowicz <a.jakubowicz@travelaudience.com>",
"license": "MIT",
"resolutions": {
"@types/node": "11.11.0"
},
"workspaces": [
"packages/*",
"client",
"server"
],
"name": "functions",
"scripts": {
"codegen": "gql2ts ./schema.graphql -o ./@types/graphql.d.ts",
"ssr": "(cd client; yarn ssr)",
"server": "(cd server; yarn start)",
"server-build": "(rm -r ./server/lib; cd server; yarn build)",
"client": "(cd client; yarn start)",
"prod-server": "(cd server; yarn prod-server)"
"lint": "tslint --project tsconfig.json",
"build": "tsc --version && tsc ; cp ./src/schema.graphql ./lib/",
"start": "nodemon --exec ts-node --files src/index.ts",
"test": "jest --runInBand",
"prod-server": "DB_USER=qpa DB_PASSWORD=qpa POSTGRES_DB=qpa forever start -v -c ts-node ./src/index.ts"
},
"main": "lib/index.js",
"dependencies": {
"gql2ts": "^1.10.1",
"typescript": "^3.4.5"
}
"@types/graphql": "^14.2.3",
"apollo-server": "^2.8.1",
"apollo-server-testing": "^2.8.1",
"atob": "^2.1.2",
"cookie": "^0.4.0",
"cors": "^2.8.5",
"graphql": "^14.4.2",
"graphql-import": "^0.7.1",
"graphql-tag": "^2.10.1",
"graphql-tools": "^4.0.5",
"luxon": "^1.17.2",
"mailgun-js": "^0.22.0",
"node-pre-gyp": "^0.13.0",
"randomstring": "^1.1.5",
"rrule": "^2.6.2",
"typeorm": "^0.2.18",
"typescript": "^3.5.3",
"uuid": "^3.3.2"
},
"devDependencies": {
"@types/jest": "^24.0.16",
"@types/luxon": "^1.15.2",
"@types/node": "^10.12.30",
"@types/uuid": "^3.4.5",
"jest": "^24.8.0",
"nodemon": "^1.19.1",
"ts-jest": "^24.0.2",
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
"typescript": "^3.5.3"
},
"jest": {
"globalSetup": "./jest-setup.js",
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testMatch": [
"**/*.spec.ts"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
},
"private": true,
"version": "1.0.0"
}

View File

@ -1,15 +0,0 @@
import MUIInput, {InputProps as MUIInputProps} from '@material-ui/core/Input'
import styled from 'cc-styled'
import * as React from 'react'
interface Props extends MUIInputProps {
}
const InputBase = (props: Props) => <MUIInput {...props} />
const Input = styled(InputBase)`
`
export default Input

View File

@ -1,11 +0,0 @@
{
"name": "cc-components",
"private": true,
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@material-ui/core": "^3.1.2",
"react": "^16.5.2"
}
}

View File

@ -1,33 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true
},
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
],
"indlude": [
"node_modules/@types"
]
}

View File

@ -1,12 +0,0 @@
import * as styledComponents from 'styled-components';
import{ default as defaultTheme, Theme } from './theme';
const {
default: styled,
css,
injectGlobal,
keyframes,
ThemeProvider
} = styledComponents as styledComponents.ThemedStyledComponentsModule<Theme>;
export { css, injectGlobal, keyframes, ThemeProvider, Theme, defaultTheme};
export default styled;

View File

@ -1,11 +0,0 @@
{
"name": "cc-styled",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@emotion/core": "^10.0.10",
"emotion": "^10.0.9",
"styled-components": "^3.4.9"
}
}

View File

@ -1,90 +0,0 @@
export interface Theme {
color: {
[K: string]: string
};
nestedDepthBackgrounds: string[];
font: {
textL: string;
textM: string;
textS: string;
textXL: string;
textXS: string;
textXXL: string;
};
input: {
height: string;
borderRadius: string;
};
chipColor: {
tag: string;
productType: string;
};
snackbar: {
borderColor: {
info: string;
debug: string;
warning: string;
error: string;
success: string;
}
}
}
const color = {
azure: '#00a9e0',
black: '#000000',
clearBlue: '#1890ff',
darkGrey: '#4a4a4a',
deepPink: '#ce0058',
dustyOrange: '#f78a27',
green: '#00a34e',
grey: '#666666',
lightBlack: '#333333',
lightGrey: '#d3d3d3',
lightSilver: '#f1f1f1',
perrywinkle: '#7fbae3',
powderBlue: '#9bcaeb',
red: '#ff0000',
silver: '#eff0f0',
tangerine: '#e95326',
warmPurple: '#6f2b8d',
white: '#fff',
yellow: '#feeb3d',
};
const theme: Theme = {
color,
font: {
textL: '16px',
textM: '14px',
textS: '13px',
textXL: '18px',
textXS: '11px',
textXXL: '20px',
},
nestedDepthBackgrounds: [
'rgba(155, 202, 235, 0.7)',
'rgba(155, 202, 235, 0.5)',
'rgba(155, 202, 235, 0.3)',
'rgba(155, 202, 235, 0.1)',
],
input: {
height: '24px',
borderRadius: '4px',
},
chipColor: {
productType: color.perrywinkle,
tag: color.azure,
},
snackbar: {
borderColor: {
success: color.green,
warning: color.yellow,
info: color.clearBlue,
debug: color.deepPink,
error: color.red,
}
}
};
export default theme;

View File

@ -1,16 +0,0 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
node_modules

2
server/.gitignore vendored
View File

@ -1,2 +0,0 @@
yarn-error.log
config.ts

View File

@ -1,72 +0,0 @@
{
"name": "functions",
"scripts": {
"lint": "tslint --project tsconfig.json",
"build": "tsc --version && tsc; cp ./src/schema.graphql ./lib/",
"start": "nodemon --exec ts-node --files src/index.ts",
"test": "jest --runInBand",
"prod-server": "DB_USER=qpa DB_PASSWORD=qpa POSTGRES_DB=qpa forever start -v -c ts-node ./src/index.ts"
},
"main": "lib/index.js",
"dependencies": {
"@types/graphql": "^14.2.3",
"apollo-server": "^2.8.1",
"apollo-server-testing": "^2.8.1",
"atob": "^2.1.2",
"axios": "^0.19.0",
"client": "0.0.1",
"cookie": "^0.4.0",
"cors": "^2.8.5",
"graphql": "^14.4.2",
"graphql-import": "^0.7.1",
"graphql-tag": "^2.10.1",
"graphql-tools": "^4.0.5",
"jsonwebtoken": "^8.5.1",
"luxon": "^1.17.2",
"mailgun-js": "^0.22.0",
"node-pre-gyp": "^0.13.0",
"pg": "^7.12.0",
"random-string": "^0.2.0",
"randomstring": "^1.1.5",
"react-dom": "^16.8.6",
"rrule": "^2.6.2",
"superagent": "^5.1.0",
"typeorm": "^0.2.18",
"typescript": "^3.5.3",
"uuid": "^3.3.2",
"webpack-node-externals": "^1.7.2"
},
"devDependencies": {
"@types/jest": "^24.0.16",
"@types/luxon": "^1.15.2",
"@types/node": "^10.12.30",
"@types/uuid": "^3.4.5",
"jest": "^24.8.0",
"nodemon": "^1.19.1",
"ts-jest": "^24.0.2",
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
"typescript": "^3.5.3"
},
"private": true,
"version": "1.0.0",
"license": "MIT",
"proxy": "https://staging.quepasaalpujarra.com",
"jest": {
"globalSetup": "../jest-setup.js",
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testMatch": [
"**/*.spec.ts"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
}
}

View File

@ -1 +0,0 @@
config.ts

View File

@ -1,124 +0,0 @@
import {auth} from 'google-auth-library'
import {OAuth2Client} from "google-auth-library/build/src/auth/oauth2client"
import {atob} from 'atob'
import { Event } from './Event.entity'
type GCalConfig = {
calendarId: string
privateKeyBase64: string
clientEmail: string
}
export default class CalendarManager {
gcalConfig: GCalConfig
gcalBaseURL: string
constructor(options: {
gcalConfig: GCalConfig,
}) {
this.gcalConfig = options.gcalConfig
this.gcalBaseURL = `https://www.googleapis.com/calendar/v3/calendars/${options.gcalConfig.calendarId}`
}
getClient = async () => {
const client: any = auth.fromJSON({
private_key: atob(this.gcalConfig.privateKeyBase64),
client_email: this.gcalConfig.clientEmail
})
client.scopes = ['https://www.googleapis.com/auth/calendar']
await client.authorize()
return client as OAuth2Client
}
createPrimaryCalendar = async () => {
const client = await this.getClient()
const res = await client.request({
method: 'post',
url: 'https://www.googleapis.com/calendar/v3/calendars',
data: {
summary: 'primary events calendar'
}
})
return res.data
}
listCalendars = async () => {
const client = await this.getClient()
const res = await client.request({
url: 'https://www.googleapis.com/calendar/v3/users/me/calendarList'
})
return (res.data as any).items
}
listEvents = async () => {
const client = await this.getClient()
let eventsResponse
try {
eventsResponse = await client.request({
method: 'get',
url: `${this.gcalBaseURL}/events`,
})
} catch (e) {
console.error('Error fetching events', e)
throw e
}
const eventsDBPromises = []
const dbIdToGCalEvents = {}
eventsResponse.data.items.forEach(gCalEvent => {
const dbId = gCalEvent.extendedProperties.private.eventId
dbIdToGCalEvents[dbId] = gCalEvent
eventsDBPromises.push(
Event.findOne(dbId)
.catch(e => {
console.warn(`Error fetching DB event ${dbId}, will skip this one`, e)
return null
})
)
})
const allEvents = await Promise.all(eventsDBPromises)
const result = allEvents.filter(Boolean).map(dbEvent => ({
...dbEvent,
...dbIdToGCalEvents[dbEvent.id]
}))
return result
}
createEvent = async (event: Event): Promise<String> => {
if (!event.id) {
throw new Error('Event doesn\'t have id')
}
const calEvent = {
// ...event.timing,
extendedProperties: {
private: {
eventId: event.id
}
}
}
let response
try {
response = await (await this.getClient()).request({
method: 'post',
url: `${this.gcalBaseURL}/events`,
data: calEvent
})
console.log('Event was saved', response.data)
} catch (e) {
console.error('There was an error saving event with gcal', e)
throw e
}
if (!(response.data && response.data.id)) {
throw new Error(`Could not save event on gcal. Event id: ${event.id}`)
}
return response.data.id
}
}

View File

@ -1,119 +0,0 @@
{
"rules": {
// -- Strict errors --
// These lint rules are likely always a good idea.
// Force function overloads to be declared together. This ensures readers understand APIs.
"adjacent-overload-signatures": true,
// Do not allow the subtle/obscure comma operator.
"ban-comma-operator": true,
// Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules.
"no-namespace": true,
// Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars.
"no-parameter-reassignment": true,
// Force the use of ES6-style imports instead of /// <reference path=> imports.
"no-reference": true,
"semicolon": [true, "never"],
// Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the
// code currently being edited (they may be incorrectly handling a different type case that does not exist).
"no-unnecessary-type-assertion": true,
// Disallow nonsensical label usage.
"label-position": true,
// Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }.
"no-conditional-assignment": true,
// Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed).
"no-construct": true,
// Do not allow super() to be called twice in a constructor.
"no-duplicate-super": true,
// Do not allow the same case to appear more than once in a switch block.
"no-duplicate-switch-case": true,
// Do not allow a variable to be declared more than once in the same block. Consider function parameters in this
// rule.
"no-duplicate-variable": [true, "check-parameters"],
// Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should
// instead use a separate variable name.
"no-shadowed-variable": true,
// Empty blocks are almost never needed. Allow the one general exception: empty catch blocks.
"no-empty": [true, "allow-empty-catch"],
// Functions must either be handled directly (e.g. with a catch() handler) or returned to another function.
// This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on.
"no-floating-promises": true,
// Do not allow any imports for modules that are not in package.json. These will almost certainly fail when
// deployed.
"no-implicit-dependencies": true,
// The 'this' keyword can only be used inside of classes.
"no-invalid-this": true,
// Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead.
"no-string-throw": true,
// Disallow control flow statements, such as return, continue, break, and throw in finally blocks.
"no-unsafe-finally": true,
// Do not allow variables to be used before they are declared.
"no-use-before-declare": true,
// Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid();
"no-void-expression": [true, "ignore-arrow-function-shorthand"],
// Disallow duplicate imports in the same file.
"no-duplicate-imports": true,
// -- Strong Warnings --
// These rules should almost never be needed, but may be included due to legacy code.
// They are left as a warning to avoid frustration with blocked deploys when the developer
// understand the warning and wants to deploy anyway.
// Warn when an empty interface is defined. These are generally not useful.
"no-empty-interface": {"severity": "warning"},
// Warn when an import will have side effects.
"no-import-side-effect": {"severity": "warning"},
// Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for
// most values and let for values that will change.
"no-var-keyword": {"severity": "warning"},
// Prefer === and !== over == and !=. The latter operators support overloads that are often accidental.
"triple-equals": {"severity": "warning"},
// Warn when using deprecated APIs.
"deprecation": {"severity": "warning"},
// -- Light Warnigns --
// These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info"
// if TSLint supported such a level.
// prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array.
// (Even better: check out utils like .map if transforming an array!)
"prefer-for-of": {"severity": "warning"},
// Warns if function overloads could be unified into a single function with optional or rest parameters.
"unified-signatures": {"severity": "warning"},
// Prefer const for values that will not change. This better documents code.
"prefer-const": {"severity": "warning"},
// Multi-line object liiterals and function calls should have a trailing comma. This helps avoid merge conflicts.
"trailing-comma": {"severity": "warning"}
},
"defaultSeverity": "error"
}

View File

@ -1,7 +1,7 @@
import SessionManager, { SessionAlreadyValidatedError } from "./SessionManager"
import { User } from "./User.entity"
import { PostOffice } from "../post_office"
import {GQL} from "../../../@types"
import {GQL} from "../../@types"
import {Context} from "../@types/graphql-utils"
interface Dependencies {

View File

@ -4,7 +4,7 @@ import {
EventOccurrence
} from "../Calendar/Event.entity"
import { Context, ResolverMap } from "../@types/graphql-utils"
import {GQL} from "../../../@types"
import {GQL} from "../../@types"
const resolvers: ResolverMap = {
Query: {
event: (_, req: GQL.IEventOnQueryArguments) => {

View File

@ -1,10 +1,10 @@
{
"compilerOptions": {
"typeRoots": ["src/@types"],
"typeRoots": ["src/@types", "node_modules/@types"],
"lib": ["es2015", "esnext.asynciterable"],
"module": "commonjs",
"noImplicitReturns": true,
"outDir": "lib",
"outDir": "../lib",
"sourceMap": true,
"target": "es6",
"allowSyntheticDefaultImports": true,
@ -14,12 +14,8 @@
"types": ["node", "jest"]
},
"compileOnSave": true,
"include": [
"src",
"migrations"
],
"files": [
"../@types/index.d.ts", "../node_modules/@types/node/ts3.2/index.d.ts"
"../@types/index.d.ts"
],
"exclude": [
"**/*.spec.ts", "node_modules", "__tests__"

28
tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"typeRoots": ["src/@types", "node_modules/@types"],
"lib": ["es2015", "esnext.asynciterable"],
"module": "commonjs",
"noImplicitReturns": true,
"outDir": "lib",
"sourceMap": true,
"target": "es6",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"types": ["node", "jest"]
},
"compileOnSave": true,
"include": [
"migrations"
],
"files": [
"@types/index.d.ts"
],
"exclude": [
"**/*.spec.ts", "node_modules", "__tests__"
]}
rm ./Auth/authHttpHandlers.js ./Auth/authResolvers.js ./Auth/Session.entity.js ./Auth/SessionManager.js ./Auth/User.entity.js ./Auth/userResolvers.js ./Calendar/Event.entity.js ./config.js ./Events/eventsResolvers.js ./graphql.js ./index.js ./ormconfig.js ./post_office.js

View File

@ -1,21 +1,119 @@
{
"extends": [
"tslint:recommended"
],
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
},
"rules": {
"member-access": false,
"object-literal-sort-keys":false,
"no-console": false,
"interface-name" : false,
"no-empty-interface" : false,
"max-classes-per-file" : false,
"semicolon": [true, "never"]
}
// -- Strict errors --
// These lint rules are likely always a good idea.
// Force function overloads to be declared together. This ensures readers understand APIs.
"adjacent-overload-signatures": true,
// Do not allow the subtle/obscure comma operator.
"ban-comma-operator": true,
// Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules.
"no-namespace": true,
// Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars.
"no-parameter-reassignment": true,
// Force the use of ES6-style imports instead of /// <reference path=> imports.
"no-reference": true,
"semicolon": [true, "never"],
// Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the
// code currently being edited (they may be incorrectly handling a different type case that does not exist).
"no-unnecessary-type-assertion": true,
// Disallow nonsensical label usage.
"label-position": true,
// Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }.
"no-conditional-assignment": true,
// Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed).
"no-construct": true,
// Do not allow super() to be called twice in a constructor.
"no-duplicate-super": true,
// Do not allow the same case to appear more than once in a switch block.
"no-duplicate-switch-case": true,
// Do not allow a variable to be declared more than once in the same block. Consider function parameters in this
// rule.
"no-duplicate-variable": [true, "check-parameters"],
// Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should
// instead use a separate variable name.
"no-shadowed-variable": true,
// Empty blocks are almost never needed. Allow the one general exception: empty catch blocks.
"no-empty": [true, "allow-empty-catch"],
// Functions must either be handled directly (e.g. with a catch() handler) or returned to another function.
// This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on.
"no-floating-promises": true,
// Do not allow any imports for modules that are not in package.json. These will almost certainly fail when
// deployed.
"no-implicit-dependencies": true,
// The 'this' keyword can only be used inside of classes.
"no-invalid-this": true,
// Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead.
"no-string-throw": true,
// Disallow control flow statements, such as return, continue, break, and throw in finally blocks.
"no-unsafe-finally": true,
// Do not allow variables to be used before they are declared.
"no-use-before-declare": true,
// Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid();
"no-void-expression": [true, "ignore-arrow-function-shorthand"],
// Disallow duplicate imports in the same file.
"no-duplicate-imports": true,
// -- Strong Warnings --
// These rules should almost never be needed, but may be included due to legacy code.
// They are left as a warning to avoid frustration with blocked deploys when the developer
// understand the warning and wants to deploy anyway.
// Warn when an empty interface is defined. These are generally not useful.
"no-empty-interface": {"severity": "warning"},
// Warn when an import will have side effects.
"no-import-side-effect": {"severity": "warning"},
// Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for
// most values and let for values that will change.
"no-var-keyword": {"severity": "warning"},
// Prefer === and !== over == and !=. The latter operators support overloads that are often accidental.
"triple-equals": {"severity": "warning"},
// Warn when using deprecated APIs.
"deprecation": {"severity": "warning"},
// -- Light Warnigns --
// These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info"
// if TSLint supported such a level.
// prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array.
// (Even better: check out utils like .map if transforming an array!)
"prefer-for-of": {"severity": "warning"},
// Warns if function overloads could be unified into a single function with optional or rest parameters.
"unified-signatures": {"severity": "warning"},
// Prefer const for values that will not change. This better documents code.
"prefer-const": {"severity": "warning"},
// Multi-line object liiterals and function calls should have a trailing comma. This helps avoid merge conflicts.
"trailing-comma": {"severity": "warning"}
},
"defaultSeverity": "error"
}

View File

@ -1,25 +0,0 @@
const path = require('path')
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: './src/index.ts',
target: 'node',
module: {
rules: [
{
test: /\.ts?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.ts']
},
output: {
filename: 'index.js', // <-- Important
libraryTarget: 'this' // <-- Important
},
externals: [nodeExternals()]
};

4801
yarn.lock

File diff suppressed because it is too large Load Diff