webui: Add API authentication

This commit is contained in:
Kevin Kandlbinder 2022-03-25 21:18:45 +01:00
parent 35a2070fb9
commit 0aa91fa99f
34 changed files with 2088 additions and 135 deletions

View file

@ -1006,15 +1006,15 @@ input EntrySort {
}
type Query {
users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection! @loggedIn
lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection! @loggedIn
entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection! @loggedIn
users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection @loggedIn
lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection @loggedIn
entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection @loggedIn
user(id: ID, username: String): User! @loggedIn
entry(id: ID, hashValue: String): Entry! @loggedIn
list(id: ID, name: String): List! @loggedIn
user(id: ID, username: String): User @loggedIn
entry(id: ID, hashValue: String): Entry @loggedIn
list(id: ID, name: String): List @loggedIn
self: User! @loggedIn
self: User @loggedIn
}
input Login {
@ -3542,14 +3542,11 @@ func (ec *executionContext) _Query_users(ctx context.Context, field graphql.Coll
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.UserConnection)
fc.Result = res
return ec.marshalNUserConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUserConnection(ctx, field.Selections, res)
return ec.marshalOUserConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUserConnection(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_lists(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -3604,14 +3601,11 @@ func (ec *executionContext) _Query_lists(ctx context.Context, field graphql.Coll
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.ListConnection)
fc.Result = res
return ec.marshalNListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx, field.Selections, res)
return ec.marshalOListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_entries(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -3666,14 +3660,11 @@ func (ec *executionContext) _Query_entries(ctx context.Context, field graphql.Co
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.EntryConnection)
fc.Result = res
return ec.marshalNEntryConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx, field.Selections, res)
return ec.marshalOEntryConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -3728,14 +3719,11 @@ func (ec *executionContext) _Query_user(ctx context.Context, field graphql.Colle
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.User)
fc.Result = res
return ec.marshalNUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx, field.Selections, res)
return ec.marshalOUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_entry(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -3790,14 +3778,11 @@ func (ec *executionContext) _Query_entry(ctx context.Context, field graphql.Coll
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.Entry)
fc.Result = res
return ec.marshalNEntry2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntry(ctx, field.Selections, res)
return ec.marshalOEntry2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntry(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_list(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -3852,14 +3837,11 @@ func (ec *executionContext) _Query_list(ctx context.Context, field graphql.Colle
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.List)
fc.Result = res
return ec.marshalNList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐList(ctx, field.Selections, res)
return ec.marshalOList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐList(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_self(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -3907,14 +3889,11 @@ func (ec *executionContext) _Query_self(ctx context.Context, field graphql.Colle
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.User)
fc.Result = res
return ec.marshalNUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx, field.Selections, res)
return ec.marshalOUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -6919,9 +6898,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
}()
res = ec._Query_users(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "lists":
@ -6933,9 +6909,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
}()
res = ec._Query_lists(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "entries":
@ -6947,9 +6920,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
}()
res = ec._Query_entries(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "user":
@ -6961,9 +6931,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
}()
res = ec._Query_user(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "entry":
@ -6975,9 +6942,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
}()
res = ec._Query_entry(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "list":
@ -6989,9 +6953,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
}()
res = ec._Query_list(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "self":
@ -7003,9 +6964,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
}()
res = ec._Query_self(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "__type":
@ -7498,20 +7456,6 @@ func (ec *executionContext) marshalNEntry2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrix
return ec._Entry(ctx, sel, v)
}
func (ec *executionContext) marshalNEntryConnection2githubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx context.Context, sel ast.SelectionSet, v model.EntryConnection) graphql.Marshaler {
return ec._EntryConnection(ctx, sel, &v)
}
func (ec *executionContext) marshalNEntryConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx context.Context, sel ast.SelectionSet, v *model.EntryConnection) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._EntryConnection(ctx, sel, v)
}
func (ec *executionContext) marshalNEntryEdge2ᚕᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.EntryEdge) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
@ -7631,20 +7575,6 @@ func (ec *executionContext) marshalNList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrix
return ec._List(ctx, sel, v)
}
func (ec *executionContext) marshalNListConnection2githubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx context.Context, sel ast.SelectionSet, v model.ListConnection) graphql.Marshaler {
return ec._ListConnection(ctx, sel, &v)
}
func (ec *executionContext) marshalNListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx context.Context, sel ast.SelectionSet, v *model.ListConnection) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ListConnection(ctx, sel, v)
}
func (ec *executionContext) marshalNListEdge2ᚕᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.ListEdge) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
@ -8170,6 +8100,13 @@ func (ec *executionContext) marshalOCommentConnection2ᚖgithubᚗcomᚋUnkn0wnC
return ec._CommentConnection(ctx, sel, v)
}
func (ec *executionContext) marshalOEntry2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntry(ctx context.Context, sel ast.SelectionSet, v *model.Entry) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._Entry(ctx, sel, v)
}
func (ec *executionContext) marshalOEntryConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx context.Context, sel ast.SelectionSet, v *model.EntryConnection) graphql.Marshaler {
if v == nil {
return graphql.Null
@ -8333,6 +8270,13 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele
return graphql.MarshalInt(*v)
}
func (ec *executionContext) marshalOList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐList(ctx context.Context, sel ast.SelectionSet, v *model.List) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._List(ctx, sel, v)
}
func (ec *executionContext) marshalOListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx context.Context, sel ast.SelectionSet, v *model.ListConnection) graphql.Marshaler {
if v == nil {
return graphql.Null
@ -8562,6 +8506,20 @@ func (ec *executionContext) unmarshalOTimestampFilter2ᚖgithubᚗcomᚋUnkn0wnC
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx context.Context, sel ast.SelectionSet, v *model.User) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._User(ctx, sel, v)
}
func (ec *executionContext) marshalOUserConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUserConnection(ctx context.Context, sel ast.SelectionSet, v *model.UserConnection) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._UserConnection(ctx, sel, v)
}
func (ec *executionContext) unmarshalOUserFilter2ᚕᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUserFilter(ctx context.Context, v interface{}) ([]*model.UserFilter, error) {
if v == nil {
return nil, nil

View file

@ -199,15 +199,15 @@ input EntrySort {
}
type Query {
users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection! @loggedIn
lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection! @loggedIn
entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection! @loggedIn
users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection @loggedIn
lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection @loggedIn
entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection @loggedIn
user(id: ID, username: String): User! @loggedIn
entry(id: ID, hashValue: String): Entry! @loggedIn
list(id: ID, name: String): List! @loggedIn
user(id: ID, username: String): User @loggedIn
entry(id: ID, hashValue: String): Entry @loggedIn
list(id: ID, name: String): List @loggedIn
self: User! @loggedIn
self: User @loggedIn
}
input Login {

View file

@ -50,7 +50,7 @@ func SetupAPI() chi.Router {
}
}
return nil, errors.New("authorization required")
return nil, nil
}
c.Directives.HasRole = func(ctx context.Context, obj interface{}, next graphql.Resolver, role model2.UserRole) (res interface{}, err error) {
@ -59,7 +59,7 @@ func SetupAPI() chi.Router {
if role == model2.UserRoleUnauthenticated {
return next(ctx)
}
return nil, errors.New("authorization required")
return nil, nil
}
switch role {
@ -75,13 +75,13 @@ func SetupAPI() chi.Router {
return nil, errors.New("server error")
}
return nil, errors.New("unauthorized")
return nil, nil
}
c.Directives.Owner = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
user, err := graph.GetUserFromContext(ctx)
if err != nil {
return nil, errors.New("authorization required")
return nil, nil
}
ctx2 := context.WithValue(ctx, "ownerConstraint", user.ID.Hex())

5
webui/.babelrc Normal file
View file

@ -0,0 +1,5 @@
{
"plugins": [
"relay"
]
}

282
webui/data/schema.graphql Normal file
View file

@ -0,0 +1,282 @@
# GraphQL schema example
#
# https://gqlgen.com/getting-started/
scalar Time
directive @loggedIn on FIELD_DEFINITION
directive @hasRole(role: UserRole!) on FIELD_DEFINITION
directive @owner on FIELD_DEFINITION
directive @maintainer on FIELD_DEFINITION
enum UserRole {
ADMIN
USER
UNAUTHENTICATED
}
enum SortDirection {
ASC
DESC
}
type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
startCursor: String!
endCursor: String!
}
input SortRule {
direction: SortDirection!
}
type User {
id: ID!
username: String!
admin: Boolean
matrixLinks: [String!]
pendingMatrixLinks: [String!]
}
type UserConnection {
pageInfo: PageInfo!
edges: [UserEdge!]!
}
type UserEdge {
node: User!
cursor: String!
}
type Entry {
id: ID!
tags: [String!]
partOf(first: Int, after: String): ListConnection
hashValue: String!
timestamp: Time!
addedBy: User!
comments(first: Int, after: String): CommentConnection
}
type EntryConnection {
pageInfo: PageInfo!
edges: [EntryEdge!]!
}
type EntryEdge {
node: Entry!
cursor: String!
}
type List {
id: ID!
name: String!
tags: [String!]
creator: User!
comments(first: Int, after: String): CommentConnection
maintainers(first: Int, after: String): UserConnection!
entries(first: Int, after: String): EntryConnection
}
type ListConnection {
pageInfo: PageInfo!
edges: [ListEdge!]!
}
type ListEdge {
node: List!
cursor: String!
}
type Comment {
timestamp: Time!
author: User!
content: String!
}
type CommentConnection {
pageInfo: PageInfo!
edges: [CommentEdge!]!
}
type CommentEdge {
node: Comment!
cursor: String!
}
input IntFilter {
gt: Int
lt: Int
eq: Int
neq: Int
}
input TimestampFilter {
after: Time
before: Time
}
input StringFilter {
eq: String # Equal
neq: String # Not Equal
regex: String # Regex Check
}
input StringArrayFilter {
containsAll: [String]
elemMatch: StringFilter
length: Int
}
input UserFilter {
id: ID
username: StringFilter
matrixLinks: StringArrayFilter
pendingMatrixLinks: StringArrayFilter
admin: Boolean
}
input UserArrayFilter {
containsAll: [UserFilter]
containsOne: [UserFilter]
length: Int
}
input UserSort {
id: SortRule
username: SortRule
admin: SortRule
}
input ListFilter {
id: ID
name: StringFilter
tags: StringArrayFilter
maintainers: IDArrayFilter
# entries: EntryArrayFilter
}
input IDArrayFilter {
containsAll: [ID]
length: Int
}
input ListArrayFilter {
containsAll: [ListFilter]
containsOne: [ListFilter]
length: Int
}
input ListSort {
id: SortRule
name: SortRule
}
input EntryFilter {
id: ID
hashValue: StringFilter
tags: StringArrayFilter
addedBy: ID
timestamp: TimestampFilter
partOf: IDArrayFilter
}
input EntryArrayFilter {
containsAll: [EntryFilter]
containsOne: [EntryFilter]
length: Int
}
input EntrySort {
id: SortRule
hashValue: SortRule
timestamp: SortRule
addedBy: SortRule
}
type Query {
users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection @loggedIn
lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection @loggedIn
entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection @loggedIn
user(id: ID, username: String): User @loggedIn
entry(id: ID, hashValue: String): Entry @loggedIn
list(id: ID, name: String): List @loggedIn
self: User @loggedIn
}
input Login {
username: String!
password: String!
}
input Register {
username: String!
password: String!
mxID: String!
}
input CreateEntry {
tags: [String!]
partOf: [ID!]
hashValue: String!
comment: String
}
input CommentEntry {
entry: ID!
comment: String!
}
input CreateList {
name: String!
tags: [String!]
comment: String
maintainers: [ID!]
entries: [ID!]
}
input CommentList {
list: ID!
comment: String!
}
input AddToLists {
lists: [ID!]!
entry: ID!
}
input RemoveFromLists {
lists: [ID!]!
entry: ID!
}
input AddMXID {
mxid: String!
}
input RemoveMXID {
mxid: String!
}
type Mutation {
login(input: Login!): String!
register(input: Register!): String! @hasRole(role: UNAUTHENTICATED)
addMXID(input: AddMXID!): User! @loggedIn
removeMXID(input: RemoveMXID!): User! @loggedIn
createEntry(input: CreateEntry!): Entry! @loggedIn
commentEntry(input: CommentEntry!): Entry! @loggedIn
addToLists(input: AddToLists!): Entry! @loggedIn
removeFromLists(input: RemoveFromLists!): Entry! @loggedIn
createList(input: CreateList!): List! @loggedIn
commentList(input: CommentList!): List! @loggedIn
deleteList(input: ID!): Boolean! @loggedIn @owner
}

View file

@ -14,6 +14,8 @@
"@types/react-dom": "^16.9.14",
"@types/react-helmet": "^6.1.5",
"@types/react-redux": "^7.1.22",
"@types/react-relay": "^13.0.1",
"@types/relay-runtime": "^13.0.2",
"axios": "^0.26.0",
"i18next": "^21.6.13",
"i18next-browser-languagedetector": "^6.1.3",
@ -25,17 +27,19 @@
"react-helmet": "^6.1.0",
"react-i18next": "^11.15.5",
"react-redux": "^7.2.6",
"react-relay": "^13.2.0",
"react-router-dom": "6",
"react-scripts": "5.0.0",
"sass": "^1.49.9",
"typescript": "~4.1.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"start": "yarn run relay && react-scripts start",
"build": "yarn run relay && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"extract-translations": "i18next --fail-on-warnings"
"extract-translations": "i18next --fail-on-warnings",
"relay": "yarn run relay-compiler $@"
},
"eslintConfig": {
"extends": "react-app"
@ -51,5 +55,10 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"babel-plugin-relay": "^13.2.0",
"graphql": "^16.3.0",
"relay-compiler": "^13.2.0"
}
}

View file

@ -1,3 +1,5 @@
{
"test": "Tst2"
"dashboard": {
"helloText": "Ayo {{name}}!"
}
}

View file

@ -1,3 +1,5 @@
{
"test": "Test"
"dashboard": {
"helloText": "Ayo {{name}}!"
}
}

5
webui/relay.config.js Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
src: "./src",
schema: "./data/schema.graphql",
language: "typescript"
}

View file

@ -1,17 +1,29 @@
import React from 'react';
import React, {useEffect} from 'react';
import { Routes, Route } from "react-router-dom";
import AuthLayout from "./layouts/AuthLayout";
import LoginView from "./components/auth/LoginView";
import RegisterView from "./components/auth/RegisterView";
import RequireAuth from "./features/auth/RequireAuth";
import {useAppDispatch} from "./app/hooks";
import {useAppDispatch, useAppSelector} from "./app/hooks";
import broadcastChannel from "./app/broadcastChannel";
import {logOut, receiveAuthUpdate} from "./features/auth/authSlice";
import {logOut, receiveAuthUpdate, selectAuth} from "./features/auth/authSlice";
import PanelLayout from "./layouts/PanelLayout";
import {Trans, useTranslation} from "react-i18next";
import {useTranslation} from "react-i18next";
import {
useQueryLoader, useRelayEnvironment,
} from 'react-relay/hooks';
import Dashboard from "./components/dashboard/Dashboard";
import DashboardQueryGraphql, {DashboardQuery} from "./components/dashboard/__generated__/DashboardQuery.graphql";
function App() {
const dispatch = useAppDispatch()
const auth = useAppSelector(selectAuth)
const environment = useRelayEnvironment();
const [dashboardInitialState, loadQuery, disposeQuery] = useQueryLoader<DashboardQuery>(
DashboardQueryGraphql
)
// This needs to be here to prevent a weird bug
useTranslation()
@ -22,6 +34,17 @@ function App() {
}
})
useEffect(() => {
if(auth.jwt !== null) {
loadQuery({})
return
}
disposeQuery()
environment.getStore().notify(undefined, true)
}, [auth])
return (
<Routes>
<Route path={"/auth"} element={<AuthLayout/>}>
@ -29,10 +52,12 @@ function App() {
<Route path={"register"} element={<RegisterView/>} />
</Route>
<Route path={"/"} element={<PanelLayout/>}>
<Route path={""} element={<RequireAuth><h1><Trans i18nKey={"test"}>Test</Trans></h1> <button onClick={() => {
<Route path={""} element={<RequireAuth>{/*<h1><Trans i18nKey={"test"}>Test</Trans></h1> <button onClick={() => {
dispatch(logOut())
}
}>Log out</button></RequireAuth>} />
}>Log out</button> <p>{
JSON.stringify(data.self)
}</p>*/}{dashboardInitialState && <Dashboard initialQueryRef={dashboardInitialState}/>}</RequireAuth>} />
<Route path={"rooms"} element={<RequireAuth><h1>rooms</h1></RequireAuth>} />
<Route path={"hashing/lists"} element={<RequireAuth><h1>lists</h1></RequireAuth>} />
<Route path={"hashing/entries"} element={<RequireAuth><h1>entries</h1></RequireAuth>} />

View file

@ -0,0 +1,22 @@
import {Environment, Network, RecordSource, Store} from 'relay-runtime';
import fetchGraphQL from "./app/fetchGraphQL";
import {RequestParameters} from "relay-runtime/lib/util/RelayConcreteNode";
import {Variables} from "relay-runtime/lib/util/RelayRuntimeTypes";
import {AnyAction, EnhancedStore} from "@reduxjs/toolkit";
import {ThunkMiddlewareFor} from "@reduxjs/toolkit/dist/getDefaultMiddleware";
const fetchRelay = (auth: GQLAuthObj) => {
return async (params: RequestParameters, variables: Variables) => {
return fetchGraphQL(auth, params.text, variables)
}
}
export type GQLAuthObj = {
store?: EnhancedStore<any, AnyAction, [ThunkMiddlewareFor<any>]>
auth?: string
}
export default (auth: GQLAuthObj) => new Environment({
network: Network.create(fetchRelay(auth)),
store: new Store(new RecordSource()),
});

View file

@ -0,0 +1,90 @@
/**
* @generated SignedSource<<92d3c9bc5ecbad19fc01cc3038c80043>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type AppMainQuery$variables = {};
export type AppMainQuery$data = {
readonly self: {
readonly admin: boolean | null;
readonly id: string;
readonly username: string;
} | null;
};
export type AppMainQuery = {
variables: AppMainQuery$variables;
response: AppMainQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "self",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "admin",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "AppMainQuery",
"selections": (v0/*: any*/),
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "AppMainQuery",
"selections": (v0/*: any*/)
},
"params": {
"cacheID": "b0e68d6eaa2fc3081f412c76778294fe",
"id": null,
"metadata": {},
"name": "AppMainQuery",
"operationKind": "query",
"text": "query AppMainQuery {\n self {\n admin\n id\n username\n }\n}\n"
}
};
})();
(node as any).hash = "4b6ec346f1ba3fa1a215bfd2f2b5bc9e";
export default node;

View file

@ -0,0 +1,97 @@
/**
* @generated SignedSource<<1ad106fe6b3ac770c83852304ee79afe>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { Fragment, ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type AppQueryComponent_lists$data = {
readonly lists: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly name: string;
readonly id: string;
readonly tags: ReadonlyArray<string> | null;
};
}>;
};
readonly " $fragmentType": "AppQueryComponent_lists";
};
export type AppQueryComponent_lists$key = {
readonly " $data"?: AppQueryComponent_lists$data;
readonly " $fragmentSpreads": FragmentRefs<"AppQueryComponent_lists">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "AppQueryComponent_lists",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ListConnection",
"kind": "LinkedField",
"name": "lists",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ListEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "List",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
};
(node as any).hash = "f7682e7e8dcd1a0ad8275bc5c03dbde8";
export default node;

View file

@ -0,0 +1,217 @@
/**
* @generated SignedSource<<de5d7bef0bc8b76c051f6343d17a8f36>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type srcListsQuery$variables = {};
export type srcListsQuery$data = {
readonly lists: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly name: string;
readonly tags: ReadonlyArray<string> | null;
readonly entries: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly hashValue: string;
};
}>;
} | null;
};
}>;
};
};
export type srcListsQuery = {
variables: srcListsQuery$variables;
response: srcListsQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"kind": "Literal",
"name": "first",
"value": 20
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = {
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 5
}
],
"concreteType": "EntryConnection",
"kind": "LinkedField",
"name": "entries",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "EntryEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Entry",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hashValue",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "entries(first:5)"
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "srcListsQuery",
"selections": [
{
"alias": null,
"args": (v0/*: any*/),
"concreteType": "ListConnection",
"kind": "LinkedField",
"name": "lists",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ListEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "List",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
(v4/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "lists(first:20)"
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "srcListsQuery",
"selections": [
{
"alias": null,
"args": (v0/*: any*/),
"concreteType": "ListConnection",
"kind": "LinkedField",
"name": "lists",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ListEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "List",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
(v4/*: any*/),
(v3/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "lists(first:20)"
}
]
},
"params": {
"cacheID": "27095434122932f164ae88c05f4cc6b9",
"id": null,
"metadata": {},
"name": "srcListsQuery",
"operationKind": "query",
"text": "query srcListsQuery {\n lists(first: 20) {\n edges {\n node {\n name\n tags\n entries(first: 5) {\n edges {\n node {\n id\n hashValue\n }\n }\n }\n id\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "ac81a444112c67683994c05979dedb6f";
export default node;

View file

@ -0,0 +1,98 @@
/**
* @generated SignedSource<<557f6510fc295600d6e0d8aa1cdad3af>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type srcUserDataQuery$variables = {};
export type srcUserDataQuery$data = {
readonly self: {
readonly id: string;
readonly username: string;
readonly matrixLinks: ReadonlyArray<string> | null;
readonly admin: boolean | null;
};
};
export type srcUserDataQuery = {
variables: srcUserDataQuery$variables;
response: srcUserDataQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "self",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "matrixLinks",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "admin",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "srcUserDataQuery",
"selections": (v0/*: any*/),
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "srcUserDataQuery",
"selections": (v0/*: any*/)
},
"params": {
"cacheID": "5a22c874dfb788e95810ee63ec866c39",
"id": null,
"metadata": {},
"name": "srcUserDataQuery",
"operationKind": "query",
"text": "query srcUserDataQuery {\n self {\n id\n username\n matrixLinks\n admin\n }\n}\n"
}
};
})();
(node as any).hash = "22115af4048ca4bee32c21e03d4cd3da";
export default node;

View file

@ -0,0 +1,29 @@
import {GQLAuthObj} from "../RelayEnvironment";
async function fetchGraphQL(auth: GQLAuthObj, text: any, variables: any) {
let headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if(auth.store && auth.store.getState().auth?.jwt) {
headers["Authorization"] = `Bearer ${auth.store.getState().auth.jwt}`
}
if(auth.auth) {
headers["Authorization"] = `Bearer ${auth.auth}`
}
const response = await fetch('http://127.0.0.1:8123/api/query', {
method: 'POST',
headers: headers,
body: JSON.stringify({
query: text,
variables,
}),
});
// Get the response as JSON
return await response.json();
}
export default fetchGraphQL;

View file

@ -4,8 +4,6 @@ import styles from "./AuthViews.module.scss";
import {ReactComponent as Logo} from "../../logo.svg";
import {Link, useLocation} from "react-router-dom";
import {axiosDefault} from "../../context/axios";
import {AxiosError} from "axios";
import {useAppDispatch} from "../../app/hooks";
import {logIn} from "../../features/auth/authSlice";
@ -13,6 +11,7 @@ import {Key} from "lucide-react";
import {AuthLocationState} from "../../layouts/AuthLayout";
import {Helmet} from "react-helmet";
import {Trans, useTranslation} from "react-i18next";
import LoginMutation from "../../mutations/LoginMutation";
const LoginView = () => {
const [username, setUsername] = useState("");
@ -34,6 +33,18 @@ const LoginView = () => {
setError("")
try {
const res = await LoginMutation(username, password)
const jwt = res.login;
dispatch(logIn(jwt))
} catch (e: any) {
setError("An error occurred: "+e.source?.errors[0]?.message)
} finally {
setLoading(false)
}
/*try {
const res = await axiosDefault.post("/auth/login", {
username,
password
@ -52,7 +63,7 @@ const LoginView = () => {
}
} finally {
setLoading(false)
}
}*/
}
return <>

View file

@ -8,12 +8,18 @@ import {AuthLocationState} from "../../layouts/AuthLayout";
import {useAppDispatch} from "../../app/hooks";
import {Helmet} from "react-helmet";
import {Trans, useTranslation} from "react-i18next";
import {logIn} from "../../features/auth/authSlice";
import RegisterMutation from "../../mutations/RegisterMutation";
import {Key} from "lucide-react";
const RegisterView = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [matrix, setMatrix] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const location = useLocation()
const locationState = location.state as AuthLocationState
@ -22,26 +28,57 @@ const RegisterView = () => {
const {t} = useTranslation()
const onSubmit = () => {
console.log(username, password, matrix)
const onSubmit = async () => {
setLoading(true)
setError("")
try {
const res = await RegisterMutation(username, password, matrix)
const jwt = res.register;
dispatch(logIn(jwt))
} catch (e: any) {
setError("An error occurred: " + e.source?.errors[0]?.message)
} finally {
setLoading(false)
}
}
return <>
<Logo width={64} height={64} />
<Logo width={64} height={64}/>
<Helmet>
<title>{t("auth:register.htmlTitle", "Register with Veles")}</title>
</Helmet>
<h1><Trans i18nKey={"auth:register.title"}>Register</Trans></h1>
<form onSubmit={(e) => {e.preventDefault(); onSubmit()}} className={styles.authForm}>
<input onChange={(ev) => setUsername(ev.target.value)} value={username} placeholder={t("auth:username", "Username")} autoCapitalize={"no"} autoCorrect={"no"} />
<input onChange={(ev) => setPassword(ev.target.value)} value={password} placeholder={t("auth:password", "Password")} type={"password"} autoCapitalize={"no"} autoCorrect={"no"} />
<input onChange={(ev) => setMatrix(ev.target.value)} value={matrix} placeholder={t("auth:matrix_handle", "Matrix-Handle")+" (@user:matrix.org)"} autoCapitalize={"no"} autoCorrect={"no"} />
<button onClick={() => onSubmit()}><Trans i18nKey={"auth:register.register"}>Register</Trans></button>
</form>
{loading && <div className={styles.loader}>
<Key/>
<span><Trans i18nKey={"auth:login.logging_in"}>Logging in...</Trans></span>
</div>}
<Link to={"/auth/login"} className={styles.mindChangedLink} aria-label={t("auth:login.login", "Login")} state={locationState}><Trans i18nKey={"auth:register.login_instead"}>I already have an account</Trans></Link>
{!loading && <>
{error !== "" && <span className={styles.error}>{error}</span>}
<form onSubmit={(e) => {
e.preventDefault();
onSubmit()
}} className={styles.authForm}>
<input onChange={(ev) => setUsername(ev.target.value)} value={username}
placeholder={t("auth:username", "Username")} autoCapitalize={"no"} autoCorrect={"no"}/>
<input onChange={(ev) => setPassword(ev.target.value)} value={password}
placeholder={t("auth:password", "Password")} type={"password"} autoCapitalize={"no"}
autoCorrect={"no"}/>
<input onChange={(ev) => setMatrix(ev.target.value)} value={matrix}
placeholder={t("auth:matrix_handle", "Matrix-Handle") + " (@user:matrix.org)"}
autoCapitalize={"no"} autoCorrect={"no"}/>
<button onClick={() => onSubmit()}><Trans i18nKey={"auth:register.register"}>Register</Trans></button>
</form>
<Link to={"/auth/login"} className={styles.mindChangedLink} aria-label={t("auth:login.login", "Login")}
state={locationState}><Trans i18nKey={"auth:register.login_instead"}>I already have an account</Trans></Link>
</>}
</>
}

View file

@ -0,0 +1,43 @@
import React from "react";
import {graphql} from "babel-plugin-relay/macro";
import {PreloadedQuery, usePreloadedQuery} from "react-relay/hooks";
import {DashboardQuery} from "./__generated__/DashboardQuery.graphql";
import {Trans} from "react-i18next";
type Props = {
initialQueryRef: PreloadedQuery<DashboardQuery>,
}
const Dashboard = (props: Props) => {
const data = usePreloadedQuery(
graphql`
query DashboardQuery {
self {
username
id
admin
}
}
`,
props.initialQueryRef
)
const name = data.self?.username
return <>
<h1><Trans i18nKey={"dashboard.helloText"}>Ayo {{name}}!</Trans></h1>
{/*<button onClick={refresh}>Refresh</button>*/}
{/*hasNext && <button
onClick={() => {
loadNext(2)
}}>
Load more Entries
</button>*/}
</>
}
export default Dashboard

View file

@ -0,0 +1,90 @@
/**
* @generated SignedSource<<3feb62450a4128509da1c04742d02894>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type DashboardQuery$variables = {};
export type DashboardQuery$data = {
readonly self: {
readonly username: string;
readonly id: string;
readonly admin: boolean | null;
} | null;
};
export type DashboardQuery = {
variables: DashboardQuery$variables;
response: DashboardQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "self",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "admin",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "DashboardQuery",
"selections": (v0/*: any*/),
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "DashboardQuery",
"selections": (v0/*: any*/)
},
"params": {
"cacheID": "30b35581f559634650cb5670a9d33fef",
"id": null,
"metadata": {},
"name": "DashboardQuery",
"operationKind": "query",
"text": "query DashboardQuery {\n self {\n username\n id\n admin\n }\n}\n"
}
};
})();
(node as any).hash = "e57a6cbc8db2ebac74f44dc969f1f0dc";
export default node;

View file

@ -0,0 +1,248 @@
/**
* @generated SignedSource<<be32aadbc100ebd4f6972d3ad100367f>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type DashboardRefreshQuery$variables = {
count?: number | null;
cursor?: string | null;
};
export type DashboardRefreshQuery$data = {
readonly " $fragmentSpreads": FragmentRefs<"Dashboard_fragment">;
};
export type DashboardRefreshQuery = {
variables: DashboardRefreshQuery$variables;
response: DashboardRefreshQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
},
v3 = [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "DashboardRefreshQuery",
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "Dashboard_fragment"
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "DashboardRefreshQuery",
"selections": [
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "name",
"value": "hello-world"
}
],
"concreteType": "List",
"kind": "LinkedField",
"name": "list",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "creator",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": "list(name:\"hello-world\")"
},
{
"alias": null,
"args": (v3/*: any*/),
"concreteType": "EntryConnection",
"kind": "LinkedField",
"name": "entries",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "EntryEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Entry",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hashValue",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "addedBy",
"plural": false,
"selections": [
(v2/*: any*/),
(v1/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": (v3/*: any*/),
"filters": null,
"handle": "connection",
"key": "Dashboard_entries",
"kind": "LinkedHandle",
"name": "entries"
}
]
},
"params": {
"cacheID": "6bcca75650c7302358635c549b4d0aeb",
"id": null,
"metadata": {},
"name": "DashboardRefreshQuery",
"operationKind": "query",
"text": "query DashboardRefreshQuery(\n $count: Int\n $cursor: String\n) {\n ...Dashboard_fragment\n}\n\nfragment Dashboard_fragment on Query {\n list(name: \"hello-world\") {\n name\n id\n tags\n creator {\n id\n username\n }\n }\n entries(after: $cursor, first: $count) {\n edges {\n node {\n id\n hashValue\n addedBy {\n username\n id\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
}
};
})();
(node as any).hash = "2c84f3121efd16285a3877f4d33f3733";
export default node;

View file

@ -0,0 +1,240 @@
/**
* @generated SignedSource<<f0d6c466b804ba5467fa45003f0bd5ca>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment, RefetchableFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type Dashboard_fragment$data = {
readonly list: {
readonly name: string;
readonly id: string;
readonly tags: ReadonlyArray<string> | null;
readonly creator: {
readonly id: string;
readonly username: string;
};
} | null;
readonly entries: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly hashValue: string;
readonly addedBy: {
readonly username: string;
};
};
}>;
} | null;
readonly " $fragmentType": "Dashboard_fragment";
};
export type Dashboard_fragment$key = {
readonly " $data"?: Dashboard_fragment$data;
readonly " $fragmentSpreads": FragmentRefs<"Dashboard_fragment">;
};
const node: ReaderFragment = (function(){
var v0 = [
"entries"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"name": "count"
},
{
"kind": "RootArgument",
"name": "cursor"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "count",
"cursor": "cursor",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "count",
"cursor": "cursor"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [],
"operation": require('./DashboardRefreshQuery.graphql')
}
},
"name": "Dashboard_fragment",
"selections": [
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "name",
"value": "hello-world"
}
],
"concreteType": "List",
"kind": "LinkedField",
"name": "list",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "creator",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": "list(name:\"hello-world\")"
},
{
"alias": "entries",
"args": null,
"concreteType": "EntryConnection",
"kind": "LinkedField",
"name": "__Dashboard_entries_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "EntryEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Entry",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hashValue",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "addedBy",
"plural": false,
"selections": [
(v2/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
};
})();
(node as any).hash = "2c84f3121efd16285a3877f4d33f3733";
export default node;

View file

@ -14,6 +14,8 @@ html, body, #root {
margin: 0;
padding: 0;
font-family: $fontMain;
background-color: var(--veles-color-background);
color: var(--veles-color-foreground);
}
:root {

View file

@ -6,17 +6,24 @@ import {store} from './app/store';
import {Provider} from 'react-redux';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from "react-router-dom";
import {
RelayEnvironmentProvider,
} from 'react-relay/hooks';
import "./i18n";
import RelayEnvironment from "./RelayEnvironment";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<React.Suspense fallback={<h1>Loading...</h1>}>
<BrowserRouter>
<App/>
</BrowserRouter>
</React.Suspense>
<RelayEnvironmentProvider environment={RelayEnvironment({store})}>
<React.Suspense fallback={<h1>Loading...</h1>}>
<BrowserRouter>
<App />
</BrowserRouter>
</React.Suspense>
</RelayEnvironmentProvider>
</Provider>
</React.StrictMode>,
document.getElementById('root')

View file

@ -12,7 +12,7 @@
left: 0;
width: 100%;
height: 100%;
z-index: -1;
z-index: 0;
background: linear-gradient(129.96deg,#ff2f2f 10.43%,#000460 92.78%),radial-gradient(100% 246.94% at 100% 0,#fff 0,#020063 100%),linear-gradient(58.72deg,#2200f2,#530000),linear-gradient(154.03deg,#b70000,#ff003d 74.04%),linear-gradient(341.1deg,red 7.52%,#0038ff 77.98%),linear-gradient(136.23deg,#00c2ff 11.12%,red 86.47%),radial-gradient(57.37% 100% at 50% 0,#b50000 0,#0034bb 100%);
background-blend-mode: overlay,color-burn,screen,overlay,difference,difference,normal;
}

View file

@ -5,7 +5,7 @@ import {Link, useLocation, useNavigate, useOutlet} from "react-router-dom";
import {UserPlus, User} from "lucide-react";
import {ReactComponent as Logo} from "../logo.svg";
import {useAppSelector} from "../app/hooks";
import {useAppDispatch, useAppSelector} from "../app/hooks";
import {selectAuth} from "../features/auth/authSlice";
import {Trans} from "react-i18next";

View file

@ -62,7 +62,7 @@
border-bottom: thin solid var(--veles-color-border);
height: 66px;
a {
a, button {
@include panelTopBarLink;
}
@ -81,6 +81,10 @@
align-items: stretch;
flex-grow: 1;
>* {
flex-shrink: 0;
}
> nav {
display: flex;
flex-direction: column;
@ -137,7 +141,10 @@
}
> main {
padding: var(--veles-layout-padding)
padding: var(--veles-layout-padding);
height: calc(100vh - 66px);
overflow: auto;
flex-grow: 1;
}
}
}

View file

@ -1,23 +1,28 @@
import React, {useState} from "react";
import {Link, NavLink, useOutlet} from "react-router-dom";
import {Home, List, ClipboardList, ExternalLink, ChevronRight, MessageSquare} from "lucide-react";
import {Home, List, ClipboardList, ExternalLink, ChevronRight, MessageSquare, LogOut} from "lucide-react";
import {ReactComponent as Logo} from "../logo.svg";
import styles from "./PanelLayout.module.scss";
import {Trans} from "react-i18next";
import {useAppDispatch} from "../app/hooks";
import {logOut} from "../features/auth/authSlice";
const PanelLayout = () => {
const outlet = useOutlet();
const [hashingExpanded, setHashingExpanded] = useState(false)
const dispatch = useAppDispatch()
return <div className={styles.panel}>
<a href={"#main"} className={styles.skipToContent}><Trans i18nKey={"panel:jump_to_content"}>Jump to Content</Trans></a>
<a href={"#navigation"} className={styles.skipToContent}><Trans i18nKey={"panel:jump_to_navigation"}>Jump to Navigation</Trans></a>
<div className={styles.topBar}>
<Link to={"/"} className={styles.logo}><Logo/> <span>Matrix-Veles</span></Link>
<a href={"https://veles.1in1.net/docs/intro"} target={"_blank"} rel={"noreferrer"}><ExternalLink/> <span><Trans i18nKey={"panel:documentation"}>Documentation</Trans></span></a>
<button onClick={() => {dispatch(logOut())}}><LogOut/> Logout</button>
</div>
<div className={styles.content}>
<nav id={"navigation"}>
@ -32,7 +37,9 @@ const PanelLayout = () => {
</div>
</nav>
<main id={"main"}>
{outlet}
<React.Suspense fallback={<span>Fetching data...</span>}>
{outlet}
</React.Suspense>
</main>
</div>
</div>

View file

@ -0,0 +1,40 @@
import {
commitMutation
} from "relay-runtime";
import {graphql} from "babel-plugin-relay/macro";
import RelayEnvironment from "../RelayEnvironment";
import {
LoginMutation as LoginMutationType,
LoginMutation$data,
LoginMutation$variables
} from "./__generated__/LoginMutation.graphql";
const mutation = graphql`
mutation LoginMutation($loginInput: Login!) {
login(input: $loginInput)
}
`
const LoginMutation = (username: string, password: string): Promise<LoginMutation$data> => {
return new Promise<LoginMutation$data>((resolve, reject) => {
const variables: LoginMutation$variables = {
loginInput: {
username,
password
}
}
commitMutation<LoginMutationType>(
RelayEnvironment({}),
{
mutation: mutation,
variables,
onCompleted: resolve,
onError: reject
}
)
})
}
export default LoginMutation

View file

@ -0,0 +1,41 @@
import {
commitMutation
} from "relay-runtime";
import {graphql} from "babel-plugin-relay/macro";
import RelayEnvironment from "../RelayEnvironment";
import {
RegisterMutation as RegisterMutationType,
RegisterMutation$data,
RegisterMutation$variables
} from "./__generated__/RegisterMutation.graphql";
const mutation = graphql`
mutation RegisterMutation($registerInput: Register!) {
register(input: $registerInput)
}
`
const RegisterMutation = (username: string, password: string, mxID: string): Promise<RegisterMutation$data> => {
return new Promise<RegisterMutation$data>((resolve, reject) => {
const variables: RegisterMutation$variables = {
registerInput: {
username,
password,
mxID
}
}
commitMutation<RegisterMutationType>(
RelayEnvironment({}),
{
mutation: mutation,
variables,
onCompleted: resolve,
onError: reject
}
)
})
}
export default RegisterMutation

View file

@ -0,0 +1,80 @@
/**
* @generated SignedSource<<4f02a0f62b7e8664299d742dc566496a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Mutation } from 'relay-runtime';
export type Login = {
username: string;
password: string;
};
export type LoginMutation$variables = {
loginInput: Login;
};
export type LoginMutation$data = {
readonly login: string;
};
export type LoginMutation = {
variables: LoginMutation$variables;
response: LoginMutation$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "loginInput"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "loginInput"
}
],
"kind": "ScalarField",
"name": "login",
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "LoginMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "LoginMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "f93ebcc6267e266343c48ca5696aa0f2",
"id": null,
"metadata": {},
"name": "LoginMutation",
"operationKind": "mutation",
"text": "mutation LoginMutation(\n $loginInput: Login!\n) {\n login(input: $loginInput)\n}\n"
}
};
})();
(node as any).hash = "f4c8734042b7e93c4d1e63db9879561a";
export default node;

View file

@ -0,0 +1,81 @@
/**
* @generated SignedSource<<a9d897ad5a18e0863fbc64c673b079ec>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Mutation } from 'relay-runtime';
export type Register = {
username: string;
password: string;
mxID: string;
};
export type RegisterMutation$variables = {
registerInput: Register;
};
export type RegisterMutation$data = {
readonly register: string;
};
export type RegisterMutation = {
variables: RegisterMutation$variables;
response: RegisterMutation$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "registerInput"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "registerInput"
}
],
"kind": "ScalarField",
"name": "register",
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "RegisterMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "RegisterMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "d49e8f643837ce5419c3a7a0a67d3bf0",
"id": null,
"metadata": {},
"name": "RegisterMutation",
"operationKind": "mutation",
"text": "mutation RegisterMutation(\n $registerInput: Register!\n) {\n register(input: $registerInput)\n}\n"
}
};
})();
(node as any).hash = "152daf2d7a917ffaafddee2b18453aa8";
export default node;

View file

@ -1 +1,4 @@
/// <reference types="react-scripts" />
declare module 'babel-plugin-relay/macro' {
export { graphql } from 'react-relay'
}

View file

@ -1024,6 +1024,13 @@
core-js-pure "^3.20.2"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.7.2":
version "7.17.8"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2"
integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.5.1", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
@ -1948,6 +1955,14 @@
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-relay@^13.0.1":
version "13.0.1"
resolved "https://registry.yarnpkg.com/@types/react-relay/-/react-relay-13.0.1.tgz#acc25817b29febdc6b251fa076ccbe6ee9b97b1f"
integrity sha512-nEjkbipJ84oOvjDz8X5bqkpaPjq3tpQekaS9P/3rlePIigttN3m3S6HMthKCeRWwXeKNdVqrMcmdPXI0TQly3g==
dependencies:
"@types/react" "*"
"@types/relay-runtime" "*"
"@types/react@*":
version "17.0.39"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce"
@ -1966,6 +1981,11 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/relay-runtime@*", "@types/relay-runtime@^13.0.2":
version "13.0.2"
resolved "https://registry.yarnpkg.com/@types/relay-runtime/-/relay-runtime-13.0.2.tgz#2b672d60ccf7cdb065fbf79724f6b34dfa79d016"
integrity sha512-3K0ONsoDb4xfGhOUfXpFQKM4xziP/YZzpbWEb0LfEC4S7n5zAn/IfQq+PISfVaU6wvSalz56lv/q/JBELzJ0RQ==
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@ -2551,7 +2571,7 @@ array.prototype.flatmap@^1.2.5:
define-properties "^1.1.3"
es-abstract "^1.19.0"
asap@~2.0.6:
asap@~2.0.3, asap@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@ -2669,6 +2689,15 @@ babel-plugin-jest-hoist@^27.5.1:
"@types/babel__core" "^7.0.0"
"@types/babel__traverse" "^7.0.6"
babel-plugin-macros@^2.0.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
dependencies:
"@babel/runtime" "^7.7.2"
cosmiconfig "^6.0.0"
resolve "^1.12.0"
babel-plugin-macros@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
@ -2707,6 +2736,15 @@ babel-plugin-polyfill-regenerator@^0.3.0:
dependencies:
"@babel/helper-define-polyfill-provider" "^0.3.1"
babel-plugin-relay@^13.2.0:
version "13.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-relay/-/babel-plugin-relay-13.2.0.tgz#2145f05373ef8e452c6aff14c9055994a14d47a7"
integrity sha512-1jGcpu0aTHkbBNtMDq7F3ZQWtVPSJfIx83lW1Qce/AMElD27JEjXd9eRPwU0/9K6JQgjLESQMjurCwJAEF8Xxw==
dependencies:
babel-plugin-macros "^2.0.0"
cosmiconfig "^5.0.5"
graphql "15.3.0"
babel-plugin-transform-react-remove-prop-types@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a"
@ -2936,6 +2974,25 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
caller-callsite@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
dependencies:
callsites "^2.0.0"
caller-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
dependencies:
caller-callsite "^2.0.0"
callsites@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@ -3320,6 +3377,16 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cosmiconfig@^5.0.5:
version "5.2.1"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
dependencies:
import-fresh "^2.0.0"
is-directory "^0.3.1"
js-yaml "^3.13.1"
parse-json "^4.0.0"
cosmiconfig@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
@ -3342,7 +3409,7 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
path-type "^4.0.0"
yaml "^1.10.0"
cross-fetch@3.1.5:
cross-fetch@3.1.5, cross-fetch@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
@ -4445,6 +4512,24 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
fbjs-css-vars@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8"
integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==
fbjs@^3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6"
integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==
dependencies:
cross-fetch "^3.1.5"
fbjs-css-vars "^1.0.0"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.30"
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@ -4825,6 +4910,16 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6,
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
graphql@15.3.0:
version "15.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278"
integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==
graphql@^16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
gulp-sort@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/gulp-sort/-/gulp-sort-2.0.0.tgz#c6762a2f1f0de0a3fc595a21599d3fac8dba1aca"
@ -5159,6 +5254,14 @@ immutable@^4.0.0:
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
import-fresh@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
dependencies:
caller-path "^2.0.0"
resolve-from "^3.0.0"
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@ -5217,6 +5320,13 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
dependencies:
loose-envify "^1.0.0"
ip@^1.1.0:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
@ -5299,6 +5409,11 @@ is-date-object@^1.0.1:
dependencies:
has-tostringtag "^1.0.0"
is-directory@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
@ -6057,7 +6172,7 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
json-parse-better-errors@^1.0.2:
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
@ -6290,7 +6405,7 @@ lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.1.0, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -6603,6 +6718,11 @@ nth-check@^2.0.1:
dependencies:
boolbase "^1.0.0"
nullthrows@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==
nwsapi@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
@ -6850,6 +6970,14 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
dependencies:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
parse-json@^5.0.0, parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
@ -7570,6 +7698,13 @@ promise-map-series@^0.3.0:
resolved "https://registry.yarnpkg.com/promise-map-series/-/promise-map-series-0.3.0.tgz#41873ca3652bb7a042b387d538552da9b576f8a1"
integrity sha512-3npG2NGhTc8BWBolLLf8l/92OxMGaRLbqvIh9wjCHhDXNvk4zsxaTaCpiCunW09qWPrN2zeNSNwRLVBrQQtutA==
promise@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
dependencies:
asap "~2.0.3"
promise@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e"
@ -7794,6 +7929,17 @@ react-refresh@^0.11.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
react-relay@^13.2.0:
version "13.2.0"
resolved "https://registry.yarnpkg.com/react-relay/-/react-relay-13.2.0.tgz#3f8cf7bb4058d293b079d107b204fb5c27d7a4e7"
integrity sha512-ldUMWZ5nIMhz1VpRylFhMGc8lEaufR0ZwwfW6YqTMU/Rsy5c5nHGN7hvktPvdUV+biy6pzdRzfFiny469gok4A==
dependencies:
"@babel/runtime" "^7.0.0"
fbjs "^3.0.2"
invariant "^2.2.4"
nullthrows "^1.1.1"
relay-runtime "13.2.0"
react-router-dom@6:
version "6.2.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.2.2.tgz#f1a2c88365593c76b9612ae80154a13fcb72e442"
@ -8004,6 +8150,20 @@ relateurl@^0.2.7:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
relay-compiler@^13.2.0:
version "13.2.0"
resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-13.2.0.tgz#06dd416e19e1cd22008b89499726ea5eb342c36e"
integrity sha512-GGLWTJYqQ5jQLMXlq++Fl17f4gMuvmIi3+xU/TrD4UqWZLI3qhxE0NFAmOvg6xgBQ8xtCMhP066kv+3bhL+7Aw==
relay-runtime@13.2.0:
version "13.2.0"
resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-13.2.0.tgz#e587e8c5cc9decdacb11227425bfe9cd5d9fa36f"
integrity sha512-iDS/fy5iWg9EhySaPpeo4Fwy1cYwNJqLHdhdGncpJBtSogFFd3fI+K0SafUoxX7+Moj6a+aMQt/JKN78HdeLhA==
dependencies:
"@babel/runtime" "^7.0.0"
fbjs "^3.0.2"
invariant "^2.2.4"
remove-bom-buffer@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53"
@ -8069,6 +8229,11 @@ resolve-cwd@^3.0.0:
dependencies:
resolve-from "^5.0.0"
resolve-from@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha1-six699nWiBvItuZTM17rywoYh0g=
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@ -8107,7 +8272,7 @@ resolve.exports@^1.1.0:
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9"
integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==
resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0:
resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@ -8361,6 +8526,11 @@ serve-static@1.14.2:
parseurl "~1.3.3"
send "0.17.2"
setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
setprototypeof@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
@ -9080,6 +9250,11 @@ typescript@~4.1.5:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.6.tgz#1becd85d77567c3c741172339e93ce2e69932138"
integrity sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow==
ua-parser-js@^0.7.30:
version "0.7.31"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6"
integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
unbox-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"