Add more stuff

This commit is contained in:
Amit Jakubowicz 2019-04-26 12:04:18 +02:00
parent ee3be1f3be
commit 4774b3a8b1
21 changed files with 292 additions and 104 deletions

View file

@ -1,4 +1,5 @@
import * as React from 'react'
import { BrowserRouter as Router, Route, Link } from "react-router-dom"
const App = () => (
<h1>Hello from app</h1>

View file

@ -0,0 +1,10 @@
import * as React from 'react'
interface Props {
from: Date
type: 'list' | 'board'
}
const Calendar = (props: Props) => (
)

View file

@ -0,0 +1,25 @@
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
}
}
}
`
interface Variables {
filter: {
limit?: number
owner?: number
from?: string
to?: string
categories?: string[]
}
}

View file

@ -0,0 +1,7 @@
import * as React from 'react'
interface Props {
from: Date
}
const List = () => ()

View file

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

View file

@ -20,7 +20,9 @@
"keycode": "^2.2.0",
"react": "^16.8.6",
"react-apollo": "^2.2.4",
"react-dom": "^16.8.6"
"react-dom": "^16.8.6",
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
@ -45,8 +47,6 @@
"babel-plugin-styled-components": "^1.10.0",
"html-webpack-plugin": "^3.2.0",
"react-hot-loader": "^4.8.2",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"ts-jest": "^24.0.0",
"ts-node": "^8.0.2",
"tslint": "^5.12.1",

View file

@ -3587,7 +3587,7 @@ he@1.2.x:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
history@^4.7.2:
history@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca"
integrity sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==
@ -3608,12 +3608,12 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5:
hoist-non-react-statics@^2.5.5:
version "2.5.5"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
hoist-non-react-statics@^3.3.0:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b"
integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==
@ -6041,7 +6041,7 @@ react-hot-loader@^4.8.2:
shallowequal "^1.0.2"
source-map "^0.7.3"
react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
@ -6051,30 +6051,34 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-router-dom@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==
react-router-dom@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.0.tgz#542a9b86af269a37f0b87218c4c25ea8dcf0c073"
integrity sha512-wSpja5g9kh5dIteZT3tUoggjnsa+TPFHSMrpHXMpFsaHhQkm/JNVGh2jiF9Dkh4+duj4MKCkwO6H08u6inZYgQ==
dependencies:
history "^4.7.2"
invariant "^2.2.4"
"@babel/runtime" "^7.1.2"
history "^4.9.0"
loose-envify "^1.3.1"
prop-types "^15.6.1"
react-router "^4.3.1"
warning "^4.0.1"
prop-types "^15.6.2"
react-router "5.0.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e"
integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==
react-router@5.0.0, react-router@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.0.tgz#349863f769ffc2fa10ee7331a4296e86bc12879d"
integrity sha512-6EQDakGdLG/it2x9EaCt9ZpEEPxnd0OCLBHQ1AcITAAx7nCnyvnzf76jKWG1s2/oJ7SSviUgfWHofdYljFexsA==
dependencies:
history "^4.7.2"
hoist-non-react-statics "^2.5.0"
invariant "^2.2.4"
"@babel/runtime" "^7.1.2"
create-react-context "^0.2.2"
history "^4.9.0"
hoist-non-react-statics "^3.1.0"
loose-envify "^1.3.1"
path-to-regexp "^1.7.0"
prop-types "^15.6.1"
warning "^4.0.1"
prop-types "^15.6.2"
react-is "^16.6.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react@^16.8.6:
version "16.8.6"
@ -7490,13 +7494,6 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
makeerror "1.0.x"
warning@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
dependencies:
loose-envify "^1.0.0"
watchpack@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"

View file

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>hello</h1>
</body>
</html>

View file

@ -8,7 +8,7 @@ const config: ConnectionOptions = {
database: process.env.POSTGRES_DB || 'qpa-dev',
entities: ["src/**/*.entity.ts"],
synchronize: true,
logging: false
logging: true
}
export const testConfig: ConnectionOptions = {

View file

@ -4,7 +4,7 @@
"codegen": "gql2ts ./src/schema.graphql -o ./src/@types/index.d.ts",
"lint": "tslint --project tsconfig.json",
"build": "tsc --version && tsc",
"start": "nodemon --exec ts-node src/index.ts",
"start": "nodemon --exec ts-node --files src/index.ts",
"test": "jest --runInBand"
},
"main": "lib/index.js",

View file

@ -1,7 +1,7 @@
// tslint:disable
// graphql typescript definitions
export namespace GQL {
declare namespace GQL {
interface IGraphQLResponseRoot {
data?: IQuery | IMutation;
errors?: Array<IGraphQLResponseError>;
@ -24,12 +24,17 @@ export namespace GQL {
__typename: 'Query';
me: IUser | null;
events: Array<ICalendarEvent | null> | null;
occurrences: Array<IEventOccurrence | null> | null;
}
interface IEventsOnQueryArguments {
filter: IEventsQueryFilter;
}
interface IOccurrencesOnQueryArguments {
filter: IOccurrencesQueryFilter;
}
interface IUser {
__typename: 'User';
name: string;
@ -39,8 +44,11 @@ export namespace GQL {
}
interface IEventsQueryFilter {
limit?: number | null;
owner?: string | null;
limit?: number | null;
from?: any | null;
to?: any | null;
categories?: Array<any | null> | null;
}
interface ICalendarEvent {
@ -52,6 +60,7 @@ export namespace GQL {
status: any;
contact: Array<IEventContactPerson>;
location: ILocation;
occurrences: Array<IEventOccurrence | null> | null;
}
interface IEventInformation {
@ -95,6 +104,25 @@ export namespace GQL {
lng: number | null;
}
interface IEventOccurrence {
__typename: 'EventOccurrence';
id: string;
event: ICalendarEvent;
start: string;
utcStart: string;
end: string;
utcEnd: string;
timeZone: string;
}
interface IOccurrencesQueryFilter {
from?: any | null;
to?: any | null;
timeZone?: any | null;
categories?: Array<any | null> | null;
limit?: number | null;
}
interface IMutation {
__typename: 'Mutation';
signup: Array<IError | null> | null;
@ -116,8 +144,7 @@ export namespace GQL {
}
interface ICreateEventOnMutationArguments {
input?: ICreateEventInput | null;
foo?: string | null;
input: ICreateEventInput;
}
interface ISignupInput {
@ -159,10 +186,10 @@ export namespace GQL {
}
interface IEventTimeInput {
timeZone?: any | null;
status?: any | null;
start?: any | null;
end?: any | null;
timeZone: any;
start: any;
end: any;
recurrence?: string | null;
}
interface IEventInformationInput {

View file

@ -7,7 +7,7 @@ export class Session extends BaseEntity {
@PrimaryGeneratedColumn()
id: string
@ManyToOne(type => User, (user: User) => user.sessions)
@ManyToOne(type => User, (user: User) => user.sessions, { eager: true})
user: User
@Column()

View file

@ -26,7 +26,7 @@ export class User extends BaseEntity {
events: Event[]
@OneToMany(type => Session, session => session.user)
sessions: Session[]
sessions: Promise<Session[]>
@OneToMany(type => SessionInvite, invite => invite.user)
sessionInvites: SessionInvite[]

View file

@ -1,6 +1,5 @@
import SessionManager, { SessionAlreadyValidatedError } from "./SessionManager"
import { User } from "./User.entity"
import { GQL } from "../@types"
import { PostOffice } from "../post_office"
interface Dependencies {

View file

@ -4,23 +4,15 @@ import {
Column,
BaseEntity,
OneToMany,
OneToOne,
ManyToOne,
CreateDateColumn,
JoinColumn,
AfterUpdate,
AfterInsert,
BeforeInsert,
BeforeUpdate,
TransactionManager,
EntityManager,
Transaction
PrimaryColumn
} from "typeorm"
import { User } from "../Auth/User.entity"
import { DateTime } from "luxon"
import { rrulestr } from "rrule"
const toUTC = (isoTime: string, ianaTZ: string) => {
export const toUTC = (isoTime: string, ianaTZ: string): string => {
const parsed = DateTime.fromISO(isoTime, { zone: ianaTZ })
if (parsed.invalidReason) {
throw new Error(
@ -71,13 +63,6 @@ export class EventTime {
exceptions?: string
}
export class EventInformation {
@Column()
title: string
@Column()
description: string
}
@Entity()
export class Event extends BaseEntity {
@PrimaryGeneratedColumn("uuid")
@ -91,8 +76,8 @@ export class Event extends BaseEntity {
})
occurrences: EventOccurrence[]
@Column(type => EventInformation)
info: EventInformation
@OneToMany(type => EventInformation, eventInfo => eventInfo.event)
info: EventInformation[]
@Column(type => EventTime)
time: EventTime
@ -121,7 +106,9 @@ export class Event extends BaseEntity {
this.occurrences = [singleOccurrence]
} else {
const ruleSet = rrulestr(this.time.recurrence)
const allDates = ruleSet.all()
const allDates = ruleSet.all(
(occurenceDate, i) => i < 30
)
this.occurrences = allDates
.map(occurenceDate => {
@ -140,6 +127,26 @@ export class Event extends BaseEntity {
}
}
@Entity()
export class EventInformation {
@PrimaryColumn()
eventIdAndLang: string
@Column()
language: string
@Column()
title: string
@Column()
description: string
@ManyToOne(type => Event, event => event.info)
event: Event
@BeforeInsert()
updatePrimaryColumn() {
this.eventIdAndLang = `${this.event.id}_${this.language}`
}
}
@Entity()
export class EventOccurrence extends BaseEntity {
@PrimaryGeneratedColumn("uuid")
@ -148,18 +155,25 @@ export class EventOccurrence extends BaseEntity {
@ManyToOne(type => Event, event => event.occurrences)
event: Event
@Column({ type: "timestamp without time zone" })
start: string
@Column({ type: "timestamptz" })
utcStart: string
@Column({ type: "timestamp without time zone" })
end: string
@Column({ type: "timestamptz" })
utcEnd: string
// Time zone of the event as entered by the user
// or implicitly by calendar's time-zone
@Column()
timeZone: string
@Column({ type: "timestamp" })
// Start date as entered by the user local to the
// event's time zone
start: string
// End date as entered by the user local to the
// event's time zone
@Column({ type: "timestamp" })
end: string
// Absolute start date in UTC used to find
// and order occurrences chronologically
@Column({ type: "timestamptz" })
utcStart: string
// Absolute end date in UTC used to find
// and order occurrences chronologically
@Column({ type: "timestamptz" })
utcEnd: string
}

View file

@ -73,7 +73,6 @@ describe("Events resolver", () => {
return (connection = await createConnection({
...testConfig
}))
})
beforeEach(async () => {
@ -150,7 +149,4 @@ describe("Events resolver", () => {
done()
})
it('Get events by user', async done => {
})
})

View file

@ -0,0 +1,55 @@
import {User} from "../../Auth/User.entity"
import {Session} from "../../Auth/Session.entity"
import {Event, toUTC} from "../../Calendar/Event.entity"
import {Frequency, RRule} from 'rrule'
import {testConfig} from "../../../ormconfig"
import {Connection, createConnection} from "typeorm"
let connection: Connection = null
describe('Occurrences', async () => {
beforeAll(async () => {
return (connection = await createConnection({
...testConfig,
}))
})
afterAll(async () => {
await connection.close()
})
it('Test occurrences resolver', async () => {
const owner = new User()
owner.name = "All the time"
owner.username = "allthetime"
owner.email = "allthetime@example.com"
await owner.save()
const session = new Session()
session.user = owner
session.hash = "allthetime_owners_auth_hash"
session.isValid = true
await session.save()
const event = new Event()
event.owner = owner
event.info = {
title: "Recurring event once a week",
description: "Event happening every monday at 13:00"
}
event.time = {
timeZone: "Europe/Madrid",
start: "2019-03-01T13:00",
end: "2019-03-01T14:00",
recurrence: new RRule({
freq: Frequency.WEEKLY,
interval: 1,
dtstart: new Date(toUTC("2019-03-01T13:00", "Europe/Madrid"))
}).toString()
}
event.status = "Scheduled"
event.updateOccurrences()
await event.save()
})
})

View file

@ -1,15 +1,34 @@
import {Event} from "../Calendar/Event.entity"
import {Context, ResolverMap} from "../@types/graphql-utils"
import {GQL} from "../@types"
import {
Event,
EventInformation,
EventOccurrence
} from "../Calendar/Event.entity"
import { Context, ResolverMap } from "../@types/graphql-utils"
const resolvers: ResolverMap = {
Query: {
events: async (_, req: GQL.IEventsOnQueryArguments, context, info) => {
return Event.find({
take: req.filter.limit,
where: (req.filter && req.filter.owner) ? `"ownerId"='${req.filter.owner}'` : null
where:
req.filter && req.filter.owner
? `"ownerId"='${req.filter.owner}'`
: null
})
},
occurrences: async (
_,
req: GQL.IOccurrencesOnQueryArguments,
context,
info
) => {
return EventOccurrence.createQueryBuilder("occurrence")
.where("occurrence.utcStart BETWEEN :from AND :to", {
from: req.filter.from,
to: req.filter.to
})
.limit(req.filter.limit)
}
},
Event: {
@ -19,15 +38,34 @@ const resolvers: ResolverMap = {
},
Mutation: {
createEvent: async (_, { input }: GQL.ICreateEventOnMutationArguments, context: Context, info) => {
createEvent: async (
_,
{ input }: GQL.ICreateEventOnMutationArguments,
context: Context,
info
) => {
if (!context.user) {
throw Error('not authenticated')
throw Error("not authenticated")
}
const event = new Event()
event.owner = context.user
event.info = input.info.map(infoInput => {
const eventInformation = new EventInformation()
eventInformation.language = infoInput.language
eventInformation.title = infoInput.title
eventInformation.description = infoInput.description
eventInformation.event = event
return eventInformation
})
event.time = {
recurrence: input.time.recurrence,
timeZone: input.time.timeZone,
start: input.time.start,
end: input.time.end
}
return null
}
},
}
}
export default resolvers

View file

@ -62,6 +62,7 @@ export const createServer = async (dependencies: Dependencies) => {
ctx.user = session.user
}
}
return ctx
}
})

View file

@ -5,6 +5,7 @@ import { sendEmail} from "./post_office"
import * as config from './config'
async function start() {
console.log(`Starting with db: ${typeormConfig.database}`)
const server = await createServer({
typeormConnection: await createConnection(typeormConfig),
sendEmail,

View file

@ -41,6 +41,17 @@ type CalendarEvent {
status: EventStatus!
contact: [EventContactPerson!]!
location: Location!
occurrences: [EventOccurrence]
}
type EventOccurrence {
id: ID!
event: CalendarEvent!
start: String!
utcStart: String!
end: String!
utcEnd: String!
timeZone: String!
}
type Location {
@ -91,16 +102,29 @@ type Contact {
phone: String
}
scalar Category
input EventsQueryFilter {
limit: Int
owner: ID
limit: Int
from: Timestamp
to: Timestamp
categories: [Category]
}
input OccurrencesQueryFilter {
from: Timestamp
to: Timestamp
timeZone: TimeZone
categories: [Category]
limit: Int
}
input EventTimeInput {
timeZone: TimeZone
status: EventStatus
start: Timestamp
end: Timestamp
timeZone: TimeZone!
start: Timestamp!
end: Timestamp!
recurrence: String
}
input EventContactInput {
@ -157,7 +181,7 @@ type Mutation {
requestInvite(input: RequestInviteInput): Boolean!
# Event
createEvent(input: CreateEventInput, foo: String): CalendarEvent
createEvent(input: CreateEventInput!): CalendarEvent
}
@ -168,4 +192,5 @@ type Query {
# Event
events(filter: EventsQueryFilter!): [CalendarEvent]
occurrences(filter: OccurrencesQueryFilter!): [EventOccurrence]
}