From fedb42538121ccc2265b974ccf7a8c4c67130a89 Mon Sep 17 00:00:00 2001 From: David Stotijn Date: Sun, 11 Oct 2020 17:09:39 +0200 Subject: [PATCH] Add project management --- README.md | 17 +- admin/src/components/Layout.tsx | 19 + admin/src/components/projects/NewProject.tsx | 122 ++ admin/src/components/projects/ProjectList.tsx | 311 ++++ admin/src/components/reqlog/LogsOverview.tsx | 20 +- admin/src/lib/graphql.ts | 7 +- admin/src/lib/theme.ts | 6 + admin/src/pages/get-started/index.tsx | 36 + admin/src/pages/index.tsx | 218 ++- admin/src/pages/projects/index.tsx | 33 + cmd/hetty/main.go | 23 +- go.mod | 5 +- go.sum | 6 + pkg/api/generated.go | 1248 +++++++++++++---- pkg/api/models_gen.go | 13 + pkg/api/resolvers.go | 79 +- pkg/api/schema.graphql | 21 + pkg/db/sqlite/sqlite.go | 65 +- pkg/proj/proj.go | 135 ++ pkg/proxy/cert.go | 4 +- pkg/reqlog/repo.go | 4 + pkg/reqlog/reqlog.go | 10 +- 22 files changed, 2080 insertions(+), 322 deletions(-) create mode 100644 admin/src/components/projects/NewProject.tsx create mode 100644 admin/src/components/projects/ProjectList.tsx create mode 100644 admin/src/pages/get-started/index.tsx create mode 100644 admin/src/pages/projects/index.tsx create mode 100644 pkg/proj/proj.go diff --git a/README.md b/README.md index 14d53af..00a5f00 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - [x] Web interface (Next.js) with proxy log viewer. - [ ] Add scope support to the proxy. - [ ] Full text search (with regex) in proxy log viewer. -- [ ] Project management. +- [x] Project management. - [ ] Sender module for sending manual HTTP requests, either from scratch or based off requests from the proxy log. - [ ] Attacker module for automated sending of HTTP requests. Leverage the concurrency @@ -56,12 +56,7 @@ Alternatively, you can run Hetty via Docker. See: [`dstotijn/hetty`](https://hub on Docker Hub. ``` -$ docker run \ --v $HOME/.hetty/hetty_key.pem:/root/.hetty/hetty_key.pem \ --v $HOME/.hetty/hetty_cert.pem:/root/.hetty/hetty_cert.pem \ --v $HOME/.hetty/hetty.db:/root/.hetty/hetty.db \ --p 127.0.0.1:8080:8080 \ -dstotijn/hetty +$ docker run -v $HOME/.hetty:/root/.hetty -p 127.0.0.1:8080:8080 dstotijn/hetty ``` ## Usage @@ -80,15 +75,15 @@ Usage of ./hetty: File path to admin build -cert string CA certificate filepath. Creates a new CA certificate is file doesn't exist (default "~/.hetty/hetty_cert.pem") - -db string - Database file path (default "~/.hetty/hetty.db") -key string CA private key filepath. Creates a new CA private key if file doesn't exist (default "~/.hetty/hetty_key.pem") + -projects string + Projects directory path (default "~/.hetty/projects") ``` ## Certificate Setup and Installation -In order for Hetty to proxy requests going to HTTPS endpoints, a root CA certificate for +In order for Hetty to proxy requests going to HTTPS endpoints, a root CA certificate for Hetty will need to be set up. Furthermore, the CA certificate may need to be installed to the host for them to be trusted by your browser. The following steps will cover how you can generate your certificate, provide them to hetty, and how @@ -115,7 +110,7 @@ certificate with hetty, simply run the command with no arguments hetty ``` -You should now have a key and certificate located at `~/.hetty/hetty_key.pem` and +You should now have a key and certificate located at `~/.hetty/hetty_key.pem` and `~/.hetty/hetty_cert.pem` respectively. #### Generating CA certificates with OpenSSL diff --git a/admin/src/components/Layout.tsx b/admin/src/components/Layout.tsx index 69fe1b6..2a32a2a 100644 --- a/admin/src/components/Layout.tsx +++ b/admin/src/components/Layout.tsx @@ -21,12 +21,15 @@ import MenuIcon from "@material-ui/icons/Menu"; import HomeIcon from "@material-ui/icons/Home"; import SettingsEthernetIcon from "@material-ui/icons/SettingsEthernet"; import SendIcon from "@material-ui/icons/Send"; +import FolderIcon from "@material-ui/icons/Folder"; import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; import ChevronRightIcon from "@material-ui/icons/ChevronRight"; import clsx from "clsx"; export enum Page { Home, + GetStarted, + Projects, ProxySetup, ProxyLogs, Sender, @@ -233,6 +236,22 @@ export function Layout({ title, page, children }: Props): JSX.Element { + + + + + + + + + +
diff --git a/admin/src/components/projects/NewProject.tsx b/admin/src/components/projects/NewProject.tsx new file mode 100644 index 0000000..1b762ca --- /dev/null +++ b/admin/src/components/projects/NewProject.tsx @@ -0,0 +1,122 @@ +import { gql, useMutation } from "@apollo/client"; +import { + Box, + Button, + CircularProgress, + createStyles, + makeStyles, + TextField, + Theme, + Typography, +} from "@material-ui/core"; +import AddIcon from "@material-ui/icons/Add"; +import React, { useState } from "react"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + projectName: { + marginTop: -6, + marginRight: theme.spacing(2), + }, + button: { + marginRight: theme.spacing(2), + }, + }) +); + +const OPEN_PROJECT = gql` + mutation OpenProject($name: String!) { + openProject(name: $name) { + name + isActive + } + } +`; + +function NewProject(): JSX.Element { + const classes = useStyles(); + const [input, setInput] = useState(null); + + const [openProject, { error, loading }] = useMutation(OPEN_PROJECT, { + onError: () => {}, + onCompleted() { + input.value = ""; + }, + update(cache, { data: { openProject } }) { + cache.modify({ + fields: { + activeProject() { + const activeProjRef = cache.writeFragment({ + id: openProject.name, + data: openProject, + fragment: gql` + fragment ActiveProject on Project { + name + isActive + type + } + `, + }); + return activeProjRef; + }, + projects(_, { DELETE }) { + cache.writeFragment({ + id: openProject.name, + data: openProject, + fragment: gql` + fragment OpenProject on Project { + name + isActive + type + } + `, + }); + return DELETE; + }, + }, + }); + }, + }); + + const handleNewProjectForm = (e: React.SyntheticEvent) => { + e.preventDefault(); + openProject({ variables: { name: input.value } }); + }; + + return ( +
+ + New project + +
+ { + setInput(node); + }, + }} + label="Project name" + placeholder="Project name…" + error={Boolean(error)} + helperText={error && error.message} + /> + + +
+ ); +} + +export default NewProject; diff --git a/admin/src/components/projects/ProjectList.tsx b/admin/src/components/projects/ProjectList.tsx new file mode 100644 index 0000000..9ae0411 --- /dev/null +++ b/admin/src/components/projects/ProjectList.tsx @@ -0,0 +1,311 @@ +import { gql, useMutation, useQuery } from "@apollo/client"; +import { + Avatar, + Box, + Button, + CircularProgress, + createStyles, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + IconButton, + List, + ListItem, + ListItemAvatar, + ListItemSecondaryAction, + ListItemText, + makeStyles, + Snackbar, + Theme, + Tooltip, + Typography, +} from "@material-ui/core"; +import CloseIcon from "@material-ui/icons/Close"; +import DescriptionIcon from "@material-ui/icons/Description"; +import DeleteIcon from "@material-ui/icons/Delete"; +import LaunchIcon from "@material-ui/icons/Launch"; +import { Alert } from "@material-ui/lab"; + +import React, { useState } from "react"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + projectsList: { + backgroundColor: theme.palette.background.paper, + }, + activeProject: { + color: theme.palette.getContrastText(theme.palette.secondary.main), + backgroundColor: theme.palette.secondary.main, + }, + deleteProjectButton: { + color: theme.palette.error.main, + }, + }) +); + +const PROJECTS = gql` + query Projects { + projects { + name + isActive + } + } +`; + +const OPEN_PROJECT = gql` + mutation OpenProject($name: String!) { + openProject(name: $name) { + name + isActive + } + } +`; + +const CLOSE_PROJECT = gql` + mutation CloseProject { + closeProject { + success + } + } +`; + +const DELETE_PROJECT = gql` + mutation DeleteProject($name: String!) { + deleteProject(name: $name) { + success + } + } +`; + +function ProjectList(): JSX.Element { + const classes = useStyles(); + const { loading: projLoading, error: projErr, data: projData } = useQuery( + PROJECTS + ); + const [ + openProject, + { error: openProjErr, loading: openProjLoading }, + ] = useMutation(OPEN_PROJECT, { + errorPolicy: "all", + onError: () => {}, + update(cache, { data: { openProject } }) { + cache.modify({ + fields: { + activeProject() { + const activeProjRef = cache.writeFragment({ + data: openProject, + fragment: gql` + fragment ActiveProject on Project { + name + isActive + type + } + `, + }); + return activeProjRef; + }, + projects(_, { DELETE }) { + cache.writeFragment({ + id: openProject.name, + data: openProject, + fragment: gql` + fragment OpenProject on Project { + name + isActive + type + } + `, + }); + return DELETE; + }, + }, + }); + }, + }); + const [closeProject, { error: closeProjErr }] = useMutation(CLOSE_PROJECT, { + errorPolicy: "all", + onError: () => {}, + update(cache) { + cache.modify({ + fields: { + activeProject() { + return null; + }, + projects(_, { DELETE }) { + return DELETE; + }, + }, + }); + }, + }); + const [ + deleteProject, + { loading: deleteProjLoading, error: deleteProjErr }, + ] = useMutation(DELETE_PROJECT, { + errorPolicy: "all", + onError: () => {}, + update(cache) { + cache.modify({ + fields: { + projects(_, { DELETE }) { + return DELETE; + }, + }, + }); + setDeleteDiagOpen(false); + setDeleteNotifOpen(true); + }, + }); + + const [deleteProjName, setDeleteProjName] = useState(null); + const [deleteDiagOpen, setDeleteDiagOpen] = useState(false); + const handleDeleteButtonClick = (name: string) => { + setDeleteProjName(name); + setDeleteDiagOpen(true); + }; + const handleDeleteConfirm = () => { + deleteProject({ variables: { name: deleteProjName } }); + }; + const handleDeleteCancel = () => { + setDeleteDiagOpen(false); + }; + + const [deleteNotifOpen, setDeleteNotifOpen] = useState(false); + const handleCloseDeleteNotif = (_, reason?: string) => { + if (reason === "clickaway") { + return; + } + setDeleteNotifOpen(false); + }; + + return ( +
+ + + Delete project “{deleteProjName}”? + + + + Deleting a project permanently removes its database file from disk. + This action is irreversible. + + {deleteProjErr && ( + + Error closing project: {deleteProjErr.message} + + )} + + + + + + + + + + Project {deleteProjName} was deleted. + + + + + Manage projects + + + + {projLoading && } + {projErr && ( + + Error fetching projects: {projErr.message} + + )} + {openProjErr && ( + + Error opening project: {openProjErr.message} + + )} + {closeProjErr && ( + + Error closing project: {closeProjErr.message} + + )} + + + {projData?.projects.length > 0 && ( + + {projData.projects.map((project) => ( + + + + + + + + {project.name} {project.isActive && (Active)} + + + {project.isActive && ( + + closeProject()}> + + + + )} + {!project.isActive && ( + + + + openProject({ + variables: { name: project.name }, + }) + } + > + + + + + )} + + + handleDeleteButtonClick(project.name)} + disabled={project.isActive} + > + + + + + + + ))} + + )} + {projData?.projects.length === 0 && ( + + There are no projects. Create one to get started. + + )} +
+ ); +} + +export default ProjectList; diff --git a/admin/src/components/reqlog/LogsOverview.tsx b/admin/src/components/reqlog/LogsOverview.tsx index 3c5efcc..f8341e3 100644 --- a/admin/src/components/reqlog/LogsOverview.tsx +++ b/admin/src/components/reqlog/LogsOverview.tsx @@ -1,7 +1,12 @@ import { useRouter } from "next/router"; import { gql, useQuery } from "@apollo/client"; -import { useState } from "react"; -import { Box, Typography, CircularProgress } from "@material-ui/core"; +import Link from "next/link"; +import { + Box, + Typography, + CircularProgress, + Link as MaterialLink, +} from "@material-ui/core"; import Alert from "@material-ui/lab/Alert"; import RequestList from "./RequestList"; @@ -42,6 +47,17 @@ function LogsOverview(): JSX.Element { return ; } if (error) { + if (error.graphQLErrors[0]?.extensions?.code === "no_active_project") { + return ( + + There is no project active.{" "} + + Create or open + {" "} + one first. + + ); + } return Error fetching logs: {error.message}; } diff --git a/admin/src/lib/graphql.ts b/admin/src/lib/graphql.ts index 3863d71..86e7a30 100644 --- a/admin/src/lib/graphql.ts +++ b/admin/src/lib/graphql.ts @@ -1,6 +1,5 @@ import { useMemo } from "react"; import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; -import { concatPagination } from "@apollo/client/utilities"; let apolloClient; @@ -12,10 +11,8 @@ function createApolloClient() { }), cache: new InMemoryCache({ typePolicies: { - Query: { - fields: { - allPosts: concatPagination(), - }, + Project: { + keyFields: ["name"], }, }, }), diff --git a/admin/src/lib/theme.ts b/admin/src/lib/theme.ts index 76c11fa..2923676 100644 --- a/admin/src/lib/theme.ts +++ b/admin/src/lib/theme.ts @@ -11,6 +11,12 @@ const theme = createMuiTheme({ secondary: { main: teal["A400"], }, + info: { + main: teal["A400"], + }, + success: { + main: teal["A400"], + }, }, typography: { h2: { diff --git a/admin/src/pages/get-started/index.tsx b/admin/src/pages/get-started/index.tsx new file mode 100644 index 0000000..bcbd9f4 --- /dev/null +++ b/admin/src/pages/get-started/index.tsx @@ -0,0 +1,36 @@ +import { Box, Link as MaterialLink, Typography } from "@material-ui/core"; +import Link from "next/link"; + +import React from "react"; + +import Layout, { Page } from "../../components/Layout"; + +function Index(): JSX.Element { + return ( + + + + Get started + + + You’ve loaded a (new) project. What’s next? You can now use the MITM + proxy and review HTTP requests and responses via the{" "} + + Proxy logs + + . Stuck? Ask for help on the{" "} + + Discussions forum + + . + + + + ); +} + +export default Index; diff --git a/admin/src/pages/index.tsx b/admin/src/pages/index.tsx index 9ab4fdb..9983dde 100644 --- a/admin/src/pages/index.tsx +++ b/admin/src/pages/index.tsx @@ -1,17 +1,30 @@ import { + Avatar, Box, Button, + CircularProgress, createStyles, - IconButton, + List, + ListItem, + ListItemAvatar, + ListItemText, makeStyles, + TextField, Theme, Typography, } from "@material-ui/core"; -import SettingsEthernetIcon from "@material-ui/icons/SettingsEthernet"; -import SendIcon from "@material-ui/icons/Send"; +import AddIcon from "@material-ui/icons/Add"; +import FolderIcon from "@material-ui/icons/Folder"; +import DescriptionIcon from "@material-ui/icons/Description"; +import PlayArrowIcon from "@material-ui/icons/PlayArrow"; import Link from "next/link"; +import { useState } from "react"; +import { gql, useMutation, useQuery } from "@apollo/client"; +import { useRouter } from "next/router"; + import Layout, { Page } from "../components/Layout"; +import { Alert } from "@material-ui/lab"; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -24,14 +37,108 @@ const useStyles = makeStyles((theme: Theme) => lineHeight: 2, marginBottom: theme.spacing(5), }, + projectName: { + marginTop: -6, + marginRight: theme.spacing(2), + }, button: { marginRight: theme.spacing(2), }, + activeProject: { + color: theme.palette.getContrastText(theme.palette.secondary.main), + backgroundColor: theme.palette.secondary.main, + }, }) ); +const ACTIVE_PROJECT = gql` + query ActiveProject { + activeProject { + name + } + } +`; + +const OPEN_PROJECT = gql` + mutation OpenProject($name: String!) { + openProject(name: $name) { + name + isActive + } + } +`; + function Index(): JSX.Element { const classes = useStyles(); + const router = useRouter(); + const [input, setInput] = useState(null); + const { error: activeProjErr, data: activeProjData } = useQuery( + ACTIVE_PROJECT, + { + pollInterval: 1000, + } + ); + const [ + openProject, + { error: openProjErr, data: openProjData, loading: openProjLoading }, + ] = useMutation(OPEN_PROJECT, { + onError: () => {}, + onCompleted({ openProject }) { + if (openProject) { + router.push("/get-started"); + } + }, + update(cache, { data: { openProject } }) { + cache.modify({ + fields: { + activeProject() { + const activeProjRef = cache.writeFragment({ + id: openProject.name, + data: openProject, + fragment: gql` + fragment ActiveProject on Project { + name + isActive + type + } + `, + }); + return activeProjRef; + }, + projects(_, { DELETE }) { + cache.writeFragment({ + id: openProject.name, + data: openProject, + fragment: gql` + fragment OpenProject on Project { + name + isActive + type + } + `, + }); + return DELETE; + }, + }, + }); + }, + }); + + const handleForm = (e: React.SyntheticEvent) => { + e.preventDefault(); + openProject({ variables: { name: input.value } }); + }; + + if (activeProjErr) { + return ( + + + Error fetching active project: {activeProjErr.message} + + + ); + } + return ( @@ -42,38 +149,105 @@ function Index(): JSX.Element { The simple HTTP toolkit for security research. + What if security testing was intuitive, powerful, and good looking? What if it was free, instead of $400 per year?{" "} Hetty is listening on{" "} :8080 - - + + {activeProjData?.activeProject?.name ? ( +
+ + Active project: + + + + + + + + + + + + + +
+ + + + + + +
+
+ ) : ( +
+ { + setInput(node); + }, + }} + label="Project name" + placeholder="Project name…" + error={Boolean(openProjErr)} + helperText={openProjErr && openProjErr.message} + /> - - - - - + + + + + )}
); diff --git a/admin/src/pages/projects/index.tsx b/admin/src/pages/projects/index.tsx new file mode 100644 index 0000000..bfc2e46 --- /dev/null +++ b/admin/src/pages/projects/index.tsx @@ -0,0 +1,33 @@ +import { Box, Divider, Grid, Typography } from "@material-ui/core"; +import Layout, { Page } from "../../components/Layout"; +import NewProject from "../../components/projects/NewProject"; +import ProjectList from "../../components/projects/ProjectList"; + +function Index(): JSX.Element { + return ( + + + + Projects + + + Projects contain settings and data generated/processed by Hetty. They + are stored as SQLite database files on disk. + + + + + + + + + + + + + + + ); +} + +export default Index; diff --git a/cmd/hetty/main.go b/cmd/hetty/main.go index c4a476b..e9e8aab 100644 --- a/cmd/hetty/main.go +++ b/cmd/hetty/main.go @@ -11,10 +11,9 @@ import ( rice "github.com/GeertJohan/go.rice" "github.com/dstotijn/hetty/pkg/api" - "github.com/dstotijn/hetty/pkg/db/sqlite" + "github.com/dstotijn/hetty/pkg/proj" "github.com/dstotijn/hetty/pkg/proxy" "github.com/dstotijn/hetty/pkg/reqlog" - "github.com/dstotijn/hetty/pkg/scope" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/playground" @@ -25,7 +24,7 @@ import ( var ( caCertFile string caKeyFile string - dbFile string + projPath string addr string adminPath string ) @@ -33,7 +32,7 @@ var ( func main() { flag.StringVar(&caCertFile, "cert", "~/.hetty/hetty_cert.pem", "CA certificate filepath. Creates a new CA certificate is file doesn't exist") flag.StringVar(&caKeyFile, "key", "~/.hetty/hetty_key.pem", "CA private key filepath. Creates a new CA private key if file doesn't exist") - flag.StringVar(&dbFile, "db", "~/.hetty/hetty.db", "Database file path") + flag.StringVar(&projPath, "projects", "~/.hetty/projects", "Projects directory path") flag.StringVar(&addr, "addr", ":8080", "TCP address to listen on, in the form \"host:port\"") flag.StringVar(&adminPath, "adminPath", "", "File path to admin build") flag.Parse() @@ -47,9 +46,9 @@ func main() { if err != nil { log.Fatalf("[FATAL] Could not parse CA private key filepath: %v", err) } - dbFile, err := homedir.Expand(dbFile) + projPath, err := homedir.Expand(projPath) if err != nil { - log.Fatalf("[FATAL] Could not parse CA private key filepath: %v", err) + log.Fatalf("[FATAL] Could not parse projects filepath: %v", err) } // Load existing CA certificate and key from disk, or generate and write @@ -59,16 +58,15 @@ func main() { log.Fatalf("[FATAL] Could not create/load CA key pair: %v", err) } - db, err := sqlite.New(dbFile) + projService, err := proj.NewService(projPath) if err != nil { - log.Fatalf("[FATAL] Could not initialize database: %v", err) + log.Fatalf("[FATAL] Could not create new project service: %v", err) } - defer db.Close() + defer projService.Close() - scope := scope.New(nil) reqLogService := reqlog.NewService(reqlog.Config{ - Scope: scope, - Repository: db, + Scope: projService.Scope, + Repository: projService.Database(), }) p, err := proxy.NewProxy(caCert, caKey) @@ -103,6 +101,7 @@ func main() { adminRouter.Path("/api/playground/").Handler(playground.Handler("GraphQL Playground", "/api/graphql/")) adminRouter.Path("/api/graphql/").Handler(handler.NewDefaultServer(api.NewExecutableSchema(api.Config{Resolvers: &api.Resolver{ RequestLogService: reqLogService, + ProjectService: projService, }}))) // Admin interface. diff --git a/go.mod b/go.mod index 77c8b7b..beada7f 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,15 @@ module github.com/dstotijn/hetty go 1.15 require ( - github.com/99designs/gqlgen v0.11.3 + github.com/99designs/gqlgen v0.13.0 github.com/GeertJohan/go.rice v1.0.0 github.com/Masterminds/squirrel v1.4.0 github.com/gorilla/mux v1.7.4 - github.com/gorilla/websocket v1.4.0 // indirect github.com/hashicorp/golang-lru v0.5.1 // indirect github.com/jmoiron/sqlx v1.2.0 github.com/mattn/go-sqlite3 v1.14.4 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.1.2 // indirect - github.com/vektah/gqlparser/v2 v2.0.1 + github.com/vektah/gqlparser/v2 v2.1.0 google.golang.org/appengine v1.6.6 // indirect ) diff --git a/go.sum b/go.sum index 1f35729..5f9e531 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4= github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4= +github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA= +github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= @@ -39,6 +41,8 @@ github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTM github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= @@ -105,6 +109,8 @@ github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWp github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o= github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= +github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns= +github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/pkg/api/generated.go b/pkg/api/generated.go index 1f86b59..0450e93 100644 --- a/pkg/api/generated.go +++ b/pkg/api/generated.go @@ -35,6 +35,7 @@ type Config struct { } type ResolverRoot interface { + Mutation() MutationResolver Query() QueryResolver } @@ -42,6 +43,14 @@ type DirectiveRoot struct { } type ComplexityRoot struct { + CloseProjectResult struct { + Success func(childComplexity int) int + } + + DeleteProjectResult struct { + Success func(childComplexity int) int + } + HTTPHeader struct { Key func(childComplexity int) int Value func(childComplexity int) int @@ -67,15 +76,35 @@ type ComplexityRoot struct { StatusReason func(childComplexity int) int } + Mutation struct { + CloseProject func(childComplexity int) int + DeleteProject func(childComplexity int, name string) int + OpenProject func(childComplexity int, name string) int + } + + Project struct { + IsActive func(childComplexity int) int + Name func(childComplexity int) int + } + Query struct { + ActiveProject func(childComplexity int) int HTTPRequestLog func(childComplexity int, id int64) int HTTPRequestLogs func(childComplexity int) int + Projects func(childComplexity int) int } } +type MutationResolver interface { + OpenProject(ctx context.Context, name string) (*Project, error) + CloseProject(ctx context.Context) (*CloseProjectResult, error) + DeleteProject(ctx context.Context, name string) (*DeleteProjectResult, error) +} type QueryResolver interface { HTTPRequestLog(ctx context.Context, id int64) (*HTTPRequestLog, error) HTTPRequestLogs(ctx context.Context) ([]HTTPRequestLog, error) + ActiveProject(ctx context.Context) (*Project, error) + Projects(ctx context.Context) ([]Project, error) } type executableSchema struct { @@ -93,6 +122,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in _ = ec switch typeName + "." + field { + case "CloseProjectResult.success": + if e.complexity.CloseProjectResult.Success == nil { + break + } + + return e.complexity.CloseProjectResult.Success(childComplexity), true + + case "DeleteProjectResult.success": + if e.complexity.DeleteProjectResult.Success == nil { + break + } + + return e.complexity.DeleteProjectResult.Success(childComplexity), true + case "HttpHeader.key": if e.complexity.HTTPHeader.Key == nil { break @@ -205,6 +248,58 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.HTTPResponseLog.StatusReason(childComplexity), true + case "Mutation.closeProject": + if e.complexity.Mutation.CloseProject == nil { + break + } + + return e.complexity.Mutation.CloseProject(childComplexity), true + + case "Mutation.deleteProject": + if e.complexity.Mutation.DeleteProject == nil { + break + } + + args, err := ec.field_Mutation_deleteProject_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.DeleteProject(childComplexity, args["name"].(string)), true + + case "Mutation.openProject": + if e.complexity.Mutation.OpenProject == nil { + break + } + + args, err := ec.field_Mutation_openProject_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.OpenProject(childComplexity, args["name"].(string)), true + + case "Project.isActive": + if e.complexity.Project.IsActive == nil { + break + } + + return e.complexity.Project.IsActive(childComplexity), true + + case "Project.name": + if e.complexity.Project.Name == nil { + break + } + + return e.complexity.Project.Name(childComplexity), true + + case "Query.activeProject": + if e.complexity.Query.ActiveProject == nil { + break + } + + return e.complexity.Query.ActiveProject(childComplexity), true + case "Query.httpRequestLog": if e.complexity.Query.HTTPRequestLog == nil { break @@ -224,6 +319,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.HTTPRequestLogs(childComplexity), true + case "Query.projects": + if e.complexity.Query.Projects == nil { + break + } + + return e.complexity.Query.Projects(childComplexity), true + } return 0, false } @@ -244,6 +346,20 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { var buf bytes.Buffer data.MarshalGQL(&buf) + return &graphql.Response{ + Data: buf.Bytes(), + } + } + case ast.Mutation: + return func(ctx context.Context) *graphql.Response { + if !first { + return nil + } + first = false + data := ec._Mutation(ctx, rc.Operation.SelectionSet) + var buf bytes.Buffer + data.MarshalGQL(&buf) + return &graphql.Response{ Data: buf.Bytes(), } @@ -274,7 +390,7 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var sources = []*ast.Source{ - &ast.Source{Name: "pkg/api/schema.graphql", Input: `type HttpRequestLog { + {Name: "pkg/api/schema.graphql", Input: `type HttpRequestLog { id: ID! url: String! method: HttpMethod! @@ -299,9 +415,30 @@ type HttpHeader { value: String! } +type Project { + name: String! + isActive: Boolean! +} + +type CloseProjectResult { + success: Boolean! +} + +type DeleteProjectResult { + success: Boolean! +} + type Query { httpRequestLog(id: ID!): HttpRequestLog httpRequestLogs: [HttpRequestLog!]! + activeProject: Project + projects: [Project!]! +} + +type Mutation { + openProject(name: String!): Project + closeProject: CloseProjectResult! + deleteProject(name: String!): DeleteProjectResult! } enum HttpMethod { @@ -325,11 +462,42 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** +func (ec *executionContext) field_Mutation_deleteProject_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["name"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["name"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Mutation_openProject_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["name"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["name"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} var arg0 string if tmp, ok := rawArgs["name"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) arg0, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err @@ -344,6 +512,7 @@ func (ec *executionContext) field_Query_httpRequestLog_args(ctx context.Context, args := map[string]interface{}{} var arg0 int64 if tmp, ok := rawArgs["id"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) arg0, err = ec.unmarshalNID2int64(ctx, tmp) if err != nil { return nil, err @@ -358,6 +527,7 @@ func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, ra args := map[string]interface{}{} var arg0 bool if tmp, ok := rawArgs["includeDeprecated"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includeDeprecated")) arg0, err = ec.unmarshalOBoolean2bool(ctx, tmp) if err != nil { return nil, err @@ -372,6 +542,7 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg args := map[string]interface{}{} var arg0 bool if tmp, ok := rawArgs["includeDeprecated"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includeDeprecated")) arg0, err = ec.unmarshalOBoolean2bool(ctx, tmp) if err != nil { return nil, err @@ -389,6 +560,76 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg // region **************************** field.gotpl ***************************** +func (ec *executionContext) _CloseProjectResult_success(ctx context.Context, field graphql.CollectedField, obj *CloseProjectResult) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "CloseProjectResult", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Success, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) _DeleteProjectResult_success(ctx context.Context, field graphql.CollectedField, obj *DeleteProjectResult) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DeleteProjectResult", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Success, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _HttpHeader_key(ctx context.Context, field graphql.CollectedField, obj *HTTPHeader) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -397,10 +638,11 @@ func (ec *executionContext) _HttpHeader_key(ctx context.Context, field graphql.C } }() fc := &graphql.FieldContext{ - Object: "HttpHeader", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpHeader", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -431,10 +673,11 @@ func (ec *executionContext) _HttpHeader_value(ctx context.Context, field graphql } }() fc := &graphql.FieldContext{ - Object: "HttpHeader", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpHeader", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -465,10 +708,11 @@ func (ec *executionContext) _HttpRequestLog_id(ctx context.Context, field graphq } }() fc := &graphql.FieldContext{ - Object: "HttpRequestLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpRequestLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -499,10 +743,11 @@ func (ec *executionContext) _HttpRequestLog_url(ctx context.Context, field graph } }() fc := &graphql.FieldContext{ - Object: "HttpRequestLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpRequestLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -533,10 +778,11 @@ func (ec *executionContext) _HttpRequestLog_method(ctx context.Context, field gr } }() fc := &graphql.FieldContext{ - Object: "HttpRequestLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpRequestLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -567,10 +813,11 @@ func (ec *executionContext) _HttpRequestLog_proto(ctx context.Context, field gra } }() fc := &graphql.FieldContext{ - Object: "HttpRequestLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpRequestLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -601,10 +848,11 @@ func (ec *executionContext) _HttpRequestLog_headers(ctx context.Context, field g } }() fc := &graphql.FieldContext{ - Object: "HttpRequestLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpRequestLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -635,10 +883,11 @@ func (ec *executionContext) _HttpRequestLog_body(ctx context.Context, field grap } }() fc := &graphql.FieldContext{ - Object: "HttpRequestLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpRequestLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -666,10 +915,11 @@ func (ec *executionContext) _HttpRequestLog_timestamp(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "HttpRequestLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpRequestLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -700,10 +950,11 @@ func (ec *executionContext) _HttpRequestLog_response(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "HttpRequestLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpRequestLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -731,10 +982,11 @@ func (ec *executionContext) _HttpResponseLog_requestId(ctx context.Context, fiel } }() fc := &graphql.FieldContext{ - Object: "HttpResponseLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpResponseLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -765,10 +1017,11 @@ func (ec *executionContext) _HttpResponseLog_proto(ctx context.Context, field gr } }() fc := &graphql.FieldContext{ - Object: "HttpResponseLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpResponseLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -799,10 +1052,11 @@ func (ec *executionContext) _HttpResponseLog_statusCode(ctx context.Context, fie } }() fc := &graphql.FieldContext{ - Object: "HttpResponseLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpResponseLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -833,10 +1087,11 @@ func (ec *executionContext) _HttpResponseLog_statusReason(ctx context.Context, f } }() fc := &graphql.FieldContext{ - Object: "HttpResponseLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpResponseLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -867,10 +1122,11 @@ func (ec *executionContext) _HttpResponseLog_body(ctx context.Context, field gra } }() fc := &graphql.FieldContext{ - Object: "HttpResponseLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpResponseLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -898,10 +1154,11 @@ func (ec *executionContext) _HttpResponseLog_headers(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "HttpResponseLog", - Field: field, - Args: nil, - IsMethod: false, + Object: "HttpResponseLog", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -924,6 +1181,192 @@ func (ec *executionContext) _HttpResponseLog_headers(ctx context.Context, field return ec.marshalNHttpHeader2ᚕgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPHeaderᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_openProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_openProject_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().OpenProject(rctx, args["name"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*Project) + fc.Result = res + return ec.marshalOProject2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProject(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation_closeProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CloseProject(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*CloseProjectResult) + fc.Result = res + return ec.marshalNCloseProjectResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCloseProjectResult(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation_deleteProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_deleteProject_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DeleteProject(rctx, args["name"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*DeleteProjectResult) + fc.Result = res + return ec.marshalNDeleteProjectResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐDeleteProjectResult(ctx, field.Selections, res) +} + +func (ec *executionContext) _Project_name(ctx context.Context, field graphql.CollectedField, obj *Project) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Project", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _Project_isActive(ctx context.Context, field graphql.CollectedField, obj *Project) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Project", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsActive, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_httpRequestLog(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -932,10 +1375,11 @@ func (ec *executionContext) _Query_httpRequestLog(ctx context.Context, field gra } }() fc := &graphql.FieldContext{ - Object: "Query", - Field: field, - Args: nil, - IsMethod: true, + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, } ctx = graphql.WithFieldContext(ctx, fc) @@ -970,10 +1414,11 @@ func (ec *executionContext) _Query_httpRequestLogs(ctx context.Context, field gr } }() fc := &graphql.FieldContext{ - Object: "Query", - Field: field, - Args: nil, - IsMethod: true, + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, } ctx = graphql.WithFieldContext(ctx, fc) @@ -996,6 +1441,73 @@ func (ec *executionContext) _Query_httpRequestLogs(ctx context.Context, field gr return ec.marshalNHttpRequestLog2ᚕgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequestLogᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _Query_activeProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().ActiveProject(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*Project) + fc.Result = res + return ec.marshalOProject2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProject(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_projects(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Projects(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]Project) + fc.Result = res + return ec.marshalNProject2ᚕgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProjectᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -1004,10 +1516,11 @@ func (ec *executionContext) _Query___type(ctx context.Context, field graphql.Col } }() fc := &graphql.FieldContext{ - Object: "Query", - Field: field, - Args: nil, - IsMethod: true, + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1042,10 +1555,11 @@ func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.C } }() fc := &graphql.FieldContext{ - Object: "Query", - Field: field, - Args: nil, - IsMethod: true, + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1073,10 +1587,11 @@ func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql } }() fc := &graphql.FieldContext{ - Object: "__Directive", - Field: field, - Args: nil, - IsMethod: false, + Object: "__Directive", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1107,10 +1622,11 @@ func (ec *executionContext) ___Directive_description(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "__Directive", - Field: field, - Args: nil, - IsMethod: false, + Object: "__Directive", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1138,10 +1654,11 @@ func (ec *executionContext) ___Directive_locations(ctx context.Context, field gr } }() fc := &graphql.FieldContext{ - Object: "__Directive", - Field: field, - Args: nil, - IsMethod: false, + Object: "__Directive", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1172,10 +1689,11 @@ func (ec *executionContext) ___Directive_args(ctx context.Context, field graphql } }() fc := &graphql.FieldContext{ - Object: "__Directive", - Field: field, - Args: nil, - IsMethod: false, + Object: "__Directive", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1206,10 +1724,11 @@ func (ec *executionContext) ___EnumValue_name(ctx context.Context, field graphql } }() fc := &graphql.FieldContext{ - Object: "__EnumValue", - Field: field, - Args: nil, - IsMethod: false, + Object: "__EnumValue", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1240,10 +1759,11 @@ func (ec *executionContext) ___EnumValue_description(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "__EnumValue", - Field: field, - Args: nil, - IsMethod: false, + Object: "__EnumValue", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1271,10 +1791,11 @@ func (ec *executionContext) ___EnumValue_isDeprecated(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "__EnumValue", - Field: field, - Args: nil, - IsMethod: true, + Object: "__EnumValue", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1305,10 +1826,11 @@ func (ec *executionContext) ___EnumValue_deprecationReason(ctx context.Context, } }() fc := &graphql.FieldContext{ - Object: "__EnumValue", - Field: field, - Args: nil, - IsMethod: true, + Object: "__EnumValue", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1336,10 +1858,11 @@ func (ec *executionContext) ___Field_name(ctx context.Context, field graphql.Col } }() fc := &graphql.FieldContext{ - Object: "__Field", - Field: field, - Args: nil, - IsMethod: false, + Object: "__Field", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1370,10 +1893,11 @@ func (ec *executionContext) ___Field_description(ctx context.Context, field grap } }() fc := &graphql.FieldContext{ - Object: "__Field", - Field: field, - Args: nil, - IsMethod: false, + Object: "__Field", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1401,10 +1925,11 @@ func (ec *executionContext) ___Field_args(ctx context.Context, field graphql.Col } }() fc := &graphql.FieldContext{ - Object: "__Field", - Field: field, - Args: nil, - IsMethod: false, + Object: "__Field", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1435,10 +1960,11 @@ func (ec *executionContext) ___Field_type(ctx context.Context, field graphql.Col } }() fc := &graphql.FieldContext{ - Object: "__Field", - Field: field, - Args: nil, - IsMethod: false, + Object: "__Field", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1469,10 +1995,11 @@ func (ec *executionContext) ___Field_isDeprecated(ctx context.Context, field gra } }() fc := &graphql.FieldContext{ - Object: "__Field", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Field", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1503,10 +2030,11 @@ func (ec *executionContext) ___Field_deprecationReason(ctx context.Context, fiel } }() fc := &graphql.FieldContext{ - Object: "__Field", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Field", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1534,10 +2062,11 @@ func (ec *executionContext) ___InputValue_name(ctx context.Context, field graphq } }() fc := &graphql.FieldContext{ - Object: "__InputValue", - Field: field, - Args: nil, - IsMethod: false, + Object: "__InputValue", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1568,10 +2097,11 @@ func (ec *executionContext) ___InputValue_description(ctx context.Context, field } }() fc := &graphql.FieldContext{ - Object: "__InputValue", - Field: field, - Args: nil, - IsMethod: false, + Object: "__InputValue", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1599,10 +2129,11 @@ func (ec *executionContext) ___InputValue_type(ctx context.Context, field graphq } }() fc := &graphql.FieldContext{ - Object: "__InputValue", - Field: field, - Args: nil, - IsMethod: false, + Object: "__InputValue", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1633,10 +2164,11 @@ func (ec *executionContext) ___InputValue_defaultValue(ctx context.Context, fiel } }() fc := &graphql.FieldContext{ - Object: "__InputValue", - Field: field, - Args: nil, - IsMethod: false, + Object: "__InputValue", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1664,10 +2196,11 @@ func (ec *executionContext) ___Schema_types(ctx context.Context, field graphql.C } }() fc := &graphql.FieldContext{ - Object: "__Schema", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Schema", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1698,10 +2231,11 @@ func (ec *executionContext) ___Schema_queryType(ctx context.Context, field graph } }() fc := &graphql.FieldContext{ - Object: "__Schema", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Schema", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1732,10 +2266,11 @@ func (ec *executionContext) ___Schema_mutationType(ctx context.Context, field gr } }() fc := &graphql.FieldContext{ - Object: "__Schema", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Schema", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1763,10 +2298,11 @@ func (ec *executionContext) ___Schema_subscriptionType(ctx context.Context, fiel } }() fc := &graphql.FieldContext{ - Object: "__Schema", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Schema", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1794,10 +2330,11 @@ func (ec *executionContext) ___Schema_directives(ctx context.Context, field grap } }() fc := &graphql.FieldContext{ - Object: "__Schema", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Schema", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1828,10 +2365,11 @@ func (ec *executionContext) ___Type_kind(ctx context.Context, field graphql.Coll } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1862,10 +2400,11 @@ func (ec *executionContext) ___Type_name(ctx context.Context, field graphql.Coll } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1893,10 +2432,11 @@ func (ec *executionContext) ___Type_description(ctx context.Context, field graph } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1924,10 +2464,11 @@ func (ec *executionContext) ___Type_fields(ctx context.Context, field graphql.Co } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1962,10 +2503,11 @@ func (ec *executionContext) ___Type_interfaces(ctx context.Context, field graphq } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -1993,10 +2535,11 @@ func (ec *executionContext) ___Type_possibleTypes(ctx context.Context, field gra } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -2024,10 +2567,11 @@ func (ec *executionContext) ___Type_enumValues(ctx context.Context, field graphq } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -2062,10 +2606,11 @@ func (ec *executionContext) ___Type_inputFields(ctx context.Context, field graph } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -2093,10 +2638,11 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co } }() fc := &graphql.FieldContext{ - Object: "__Type", - Field: field, - Args: nil, - IsMethod: true, + Object: "__Type", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: false, } ctx = graphql.WithFieldContext(ctx, fc) @@ -2128,6 +2674,60 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co // region **************************** object.gotpl **************************** +var closeProjectResultImplementors = []string{"CloseProjectResult"} + +func (ec *executionContext) _CloseProjectResult(ctx context.Context, sel ast.SelectionSet, obj *CloseProjectResult) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, closeProjectResultImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CloseProjectResult") + case "success": + out.Values[i] = ec._CloseProjectResult_success(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var deleteProjectResultImplementors = []string{"DeleteProjectResult"} + +func (ec *executionContext) _DeleteProjectResult(ctx context.Context, sel ast.SelectionSet, obj *DeleteProjectResult) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, deleteProjectResultImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DeleteProjectResult") + case "success": + out.Values[i] = ec._DeleteProjectResult_success(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var httpHeaderImplementors = []string{"HttpHeader"} func (ec *executionContext) _HttpHeader(ctx context.Context, sel ast.SelectionSet, obj *HTTPHeader) graphql.Marshaler { @@ -2265,6 +2865,76 @@ func (ec *executionContext) _HttpResponseLog(ctx context.Context, sel ast.Select return out } +var mutationImplementors = []string{"Mutation"} + +func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mutationImplementors) + + ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ + Object: "Mutation", + }) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Mutation") + case "openProject": + out.Values[i] = ec._Mutation_openProject(ctx, field) + case "closeProject": + out.Values[i] = ec._Mutation_closeProject(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "deleteProject": + out.Values[i] = ec._Mutation_deleteProject(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var projectImplementors = []string{"Project"} + +func (ec *executionContext) _Project(ctx context.Context, sel ast.SelectionSet, obj *Project) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, projectImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Project") + case "name": + out.Values[i] = ec._Project_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "isActive": + out.Values[i] = ec._Project_isActive(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var queryImplementors = []string{"Query"} func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -2305,6 +2975,31 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "activeProject": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_activeProject(ctx, field) + return res + }) + case "projects": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_projects(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "__type": out.Values[i] = ec._Query___type(ctx, field) case "__schema": @@ -2566,7 +3261,8 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o // region ***************************** type.gotpl ***************************** func (ec *executionContext) unmarshalNBoolean2bool(ctx context.Context, v interface{}) (bool, error) { - return graphql.UnmarshalBoolean(v) + res, err := graphql.UnmarshalBoolean(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.SelectionSet, v bool) graphql.Marshaler { @@ -2579,6 +3275,34 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) marshalNCloseProjectResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCloseProjectResult(ctx context.Context, sel ast.SelectionSet, v CloseProjectResult) graphql.Marshaler { + return ec._CloseProjectResult(ctx, sel, &v) +} + +func (ec *executionContext) marshalNCloseProjectResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCloseProjectResult(ctx context.Context, sel ast.SelectionSet, v *CloseProjectResult) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._CloseProjectResult(ctx, sel, v) +} + +func (ec *executionContext) marshalNDeleteProjectResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐDeleteProjectResult(ctx context.Context, sel ast.SelectionSet, v DeleteProjectResult) graphql.Marshaler { + return ec._DeleteProjectResult(ctx, sel, &v) +} + +func (ec *executionContext) marshalNDeleteProjectResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐDeleteProjectResult(ctx context.Context, sel ast.SelectionSet, v *DeleteProjectResult) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._DeleteProjectResult(ctx, sel, v) +} + func (ec *executionContext) marshalNHttpHeader2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPHeader(ctx context.Context, sel ast.SelectionSet, v HTTPHeader) graphql.Marshaler { return ec._HttpHeader(ctx, sel, &v) } @@ -2622,7 +3346,8 @@ func (ec *executionContext) marshalNHttpHeader2ᚕgithubᚗcomᚋdstotijnᚋhett func (ec *executionContext) unmarshalNHttpMethod2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPMethod(ctx context.Context, v interface{}) (HTTPMethod, error) { var res HTTPMethod - return res, res.UnmarshalGQL(v) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalNHttpMethod2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPMethod(ctx context.Context, sel ast.SelectionSet, v HTTPMethod) graphql.Marshaler { @@ -2671,7 +3396,8 @@ func (ec *executionContext) marshalNHttpRequestLog2ᚕgithubᚗcomᚋdstotijnᚋ } func (ec *executionContext) unmarshalNID2int64(ctx context.Context, v interface{}) (int64, error) { - return graphql.UnmarshalInt64(v) + res, err := graphql.UnmarshalInt64(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalNID2int64(ctx context.Context, sel ast.SelectionSet, v int64) graphql.Marshaler { @@ -2685,7 +3411,8 @@ func (ec *executionContext) marshalNID2int64(ctx context.Context, sel ast.Select } func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) { - return graphql.UnmarshalInt(v) + res, err := graphql.UnmarshalInt(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler { @@ -2698,8 +3425,50 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti return res } +func (ec *executionContext) marshalNProject2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProject(ctx context.Context, sel ast.SelectionSet, v Project) graphql.Marshaler { + return ec._Project(ctx, sel, &v) +} + +func (ec *executionContext) marshalNProject2ᚕgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProjectᚄ(ctx context.Context, sel ast.SelectionSet, v []Project) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNProject2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProject(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { - return graphql.UnmarshalString(v) + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { @@ -2713,7 +3482,8 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S } func (ec *executionContext) unmarshalNTime2timeᚐTime(ctx context.Context, v interface{}) (time.Time, error) { - return graphql.UnmarshalTime(v) + res, err := graphql.UnmarshalTime(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel ast.SelectionSet, v time.Time) graphql.Marshaler { @@ -2768,7 +3538,8 @@ func (ec *executionContext) marshalN__Directive2ᚕgithubᚗcomᚋ99designsᚋgq } func (ec *executionContext) unmarshalN__DirectiveLocation2string(ctx context.Context, v interface{}) (string, error) { - return graphql.UnmarshalString(v) + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalN__DirectiveLocation2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { @@ -2793,6 +3564,7 @@ func (ec *executionContext) unmarshalN__DirectiveLocation2ᚕstringᚄ(ctx conte var err error res := make([]string, len(vSlice)) for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) res[i], err = ec.unmarshalN__DirectiveLocation2string(ctx, vSlice[i]) if err != nil { return nil, err @@ -2939,7 +3711,8 @@ func (ec *executionContext) marshalN__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgen } func (ec *executionContext) unmarshalN__TypeKind2string(ctx context.Context, v interface{}) (string, error) { - return graphql.UnmarshalString(v) + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { @@ -2953,7 +3726,8 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a } func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) { - return graphql.UnmarshalBoolean(v) + res, err := graphql.UnmarshalBoolean(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalOBoolean2bool(ctx context.Context, sel ast.SelectionSet, v bool) graphql.Marshaler { @@ -2964,19 +3738,15 @@ func (ec *executionContext) unmarshalOBoolean2ᚖbool(ctx context.Context, v int if v == nil { return nil, nil } - res, err := ec.unmarshalOBoolean2bool(ctx, v) - return &res, err + res, err := graphql.UnmarshalBoolean(v) + return &res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast.SelectionSet, v *bool) graphql.Marshaler { if v == nil { return graphql.Null } - return ec.marshalOBoolean2bool(ctx, sel, *v) -} - -func (ec *executionContext) marshalOHttpRequestLog2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequestLog(ctx context.Context, sel ast.SelectionSet, v HTTPRequestLog) graphql.Marshaler { - return ec._HttpRequestLog(ctx, sel, &v) + return graphql.MarshalBoolean(*v) } func (ec *executionContext) marshalOHttpRequestLog2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequestLog(ctx context.Context, sel ast.SelectionSet, v *HTTPRequestLog) graphql.Marshaler { @@ -2986,10 +3756,6 @@ func (ec *executionContext) marshalOHttpRequestLog2ᚖgithubᚗcomᚋdstotijnᚋ return ec._HttpRequestLog(ctx, sel, v) } -func (ec *executionContext) marshalOHttpResponseLog2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPResponseLog(ctx context.Context, sel ast.SelectionSet, v HTTPResponseLog) graphql.Marshaler { - return ec._HttpResponseLog(ctx, sel, &v) -} - func (ec *executionContext) marshalOHttpResponseLog2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPResponseLog(ctx context.Context, sel ast.SelectionSet, v *HTTPResponseLog) graphql.Marshaler { if v == nil { return graphql.Null @@ -2997,8 +3763,16 @@ func (ec *executionContext) marshalOHttpResponseLog2ᚖgithubᚗcomᚋdstotijn return ec._HttpResponseLog(ctx, sel, v) } +func (ec *executionContext) marshalOProject2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProject(ctx context.Context, sel ast.SelectionSet, v *Project) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Project(ctx, sel, v) +} + func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) { - return graphql.UnmarshalString(v) + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalOString2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { @@ -3009,15 +3783,15 @@ func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v in if v == nil { return nil, nil } - res, err := ec.unmarshalOString2string(ctx, v) - return &res, err + res, err := graphql.UnmarshalString(v) + return &res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel ast.SelectionSet, v *string) graphql.Marshaler { if v == nil { return graphql.Null } - return ec.marshalOString2string(ctx, sel, *v) + return graphql.MarshalString(*v) } func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler { @@ -3140,10 +3914,6 @@ func (ec *executionContext) marshalO__InputValue2ᚕgithubᚗcomᚋ99designsᚋg return ret } -func (ec *executionContext) marshalO__Schema2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx context.Context, sel ast.SelectionSet, v introspection.Schema) graphql.Marshaler { - return ec.___Schema(ctx, sel, &v) -} - func (ec *executionContext) marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx context.Context, sel ast.SelectionSet, v *introspection.Schema) graphql.Marshaler { if v == nil { return graphql.Null @@ -3151,10 +3921,6 @@ func (ec *executionContext) marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlge return ec.___Schema(ctx, sel, v) } -func (ec *executionContext) marshalO__Type2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx context.Context, sel ast.SelectionSet, v introspection.Type) graphql.Marshaler { - return ec.___Type(ctx, sel, &v) -} - func (ec *executionContext) marshalO__Type2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐTypeᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.Type) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/pkg/api/models_gen.go b/pkg/api/models_gen.go index 902b3a7..46a7266 100644 --- a/pkg/api/models_gen.go +++ b/pkg/api/models_gen.go @@ -9,6 +9,14 @@ import ( "time" ) +type CloseProjectResult struct { + Success bool `json:"success"` +} + +type DeleteProjectResult struct { + Success bool `json:"success"` +} + type HTTPHeader struct { Key string `json:"key"` Value string `json:"value"` @@ -34,6 +42,11 @@ type HTTPResponseLog struct { Headers []HTTPHeader `json:"headers"` } +type Project struct { + Name string `json:"name"` + IsActive bool `json:"isActive"` +} + type HTTPMethod string const ( diff --git a/pkg/api/resolvers.go b/pkg/api/resolvers.go index 5dfd303..93f4fa0 100644 --- a/pkg/api/resolvers.go +++ b/pkg/api/resolvers.go @@ -7,20 +7,35 @@ import ( "fmt" "strings" + "github.com/99designs/gqlgen/graphql" + "github.com/dstotijn/hetty/pkg/proj" "github.com/dstotijn/hetty/pkg/reqlog" + "github.com/vektah/gqlparser/v2/gqlerror" ) type Resolver struct { RequestLogService *reqlog.Service + ProjectService *proj.Service } type queryResolver struct{ *Resolver } +type mutationResolver struct{ *Resolver } -func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } +func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } +func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } func (r *queryResolver) HTTPRequestLogs(ctx context.Context) ([]HTTPRequestLog, error) { opts := reqlog.FindRequestsOptions{OmitOutOfScope: false} reqs, err := r.RequestLogService.FindRequests(ctx, opts) + if err == reqlog.ErrNoProject { + return nil, &gqlerror.Error{ + Path: graphql.GetPath(ctx), + Message: "No active project.", + Extensions: map[string]interface{}{ + "code": "no_active_project", + }, + } + } if err != nil { return nil, fmt.Errorf("could not query repository for requests: %v", err) } @@ -116,3 +131,65 @@ func parseRequestLog(req reqlog.Request) (HTTPRequestLog, error) { return log, nil } + +func (r *mutationResolver) OpenProject(ctx context.Context, name string) (*Project, error) { + p, err := r.ProjectService.Open(name) + if err == proj.ErrInvalidName { + return nil, gqlerror.Errorf("Project name must only contain alphanumeric or space chars.") + } + if err != nil { + return nil, fmt.Errorf("could not open project: %v", err) + } + return &Project{ + Name: p.Name, + IsActive: p.IsActive, + }, nil +} + +func (r *queryResolver) ActiveProject(ctx context.Context) (*Project, error) { + p, err := r.ProjectService.ActiveProject() + if err == proj.ErrNoProject { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("could not open project: %v", err) + } + + return &Project{ + Name: p.Name, + IsActive: p.IsActive, + }, nil +} + +func (r *queryResolver) Projects(ctx context.Context) ([]Project, error) { + p, err := r.ProjectService.Projects() + if err != nil { + return nil, fmt.Errorf("could not get projects: %v", err) + } + + projects := make([]Project, len(p)) + for i, proj := range p { + projects[i] = Project{ + Name: proj.Name, + IsActive: proj.IsActive, + } + } + + return projects, nil +} + +func (r *mutationResolver) CloseProject(ctx context.Context) (*CloseProjectResult, error) { + if err := r.ProjectService.Close(); err != nil { + return nil, fmt.Errorf("could not close project: %v", err) + } + return &CloseProjectResult{true}, nil +} + +func (r *mutationResolver) DeleteProject(ctx context.Context, name string) (*DeleteProjectResult, error) { + if err := r.ProjectService.Delete(name); err != nil { + return nil, fmt.Errorf("could not delete project: %v", err) + } + return &DeleteProjectResult{ + Success: true, + }, nil +} diff --git a/pkg/api/schema.graphql b/pkg/api/schema.graphql index 07ccff1..edc9859 100644 --- a/pkg/api/schema.graphql +++ b/pkg/api/schema.graphql @@ -23,9 +23,30 @@ type HttpHeader { value: String! } +type Project { + name: String! + isActive: Boolean! +} + +type CloseProjectResult { + success: Boolean! +} + +type DeleteProjectResult { + success: Boolean! +} + type Query { httpRequestLog(id: ID!): HttpRequestLog httpRequestLogs: [HttpRequestLog!]! + activeProject: Project + projects: [Project!]! +} + +type Mutation { + openProject(name: String!): Project + closeProject: CloseProjectResult! + deleteProject(name: String!): DeleteProjectResult! } enum HttpMethod { diff --git a/pkg/db/sqlite/sqlite.go b/pkg/db/sqlite/sqlite.go index dbe97fe..bec3904 100644 --- a/pkg/db/sqlite/sqlite.go +++ b/pkg/db/sqlite/sqlite.go @@ -3,11 +3,10 @@ package sqlite import ( "context" "database/sql" + "errors" "fmt" "net/http" "net/url" - "os" - "path/filepath" "time" "github.com/dstotijn/hetty/pkg/reqlog" @@ -33,13 +32,10 @@ type httpRequestLogsQuery struct { joinResponse bool } -// New returns a new Client. -func New(filename string) (*Client, error) { - // Create directory for DB if it doesn't exist yet. - if dbDir, _ := filepath.Split(filename); dbDir != "" { - if _, err := os.Stat(dbDir); os.IsNotExist(err) { - os.Mkdir(dbDir, 0755) - } +// Open opens a database. +func (c *Client) Open(filename string) error { + if c.db != nil { + return errors.New("sqlite: database already open") } opts := make(url.Values) @@ -48,24 +44,24 @@ func New(filename string) (*Client, error) { dsn := fmt.Sprintf("file:%v?%v", filename, opts.Encode()) db, err := sqlx.Open("sqlite3", dsn) if err != nil { - return nil, err + return fmt.Errorf("sqlite: could not open database: %v", err) } if err := db.Ping(); err != nil { - return nil, fmt.Errorf("sqlite: could not ping database: %v", err) + return fmt.Errorf("sqlite: could not ping database: %v", err) } - c := &Client{db: db} - - if err := c.prepareSchema(); err != nil { - return nil, fmt.Errorf("sqlite: could not prepare schema: %v", err) + if err := prepareSchema(db); err != nil { + return fmt.Errorf("sqlite: could not prepare schema: %v", err) } - return &Client{db: db}, nil + c.db = db + + return nil } -func (c Client) prepareSchema() error { - _, err := c.db.Exec(`CREATE TABLE IF NOT EXISTS http_requests ( +func prepareSchema(db *sqlx.DB) error { + _, err := db.Exec(`CREATE TABLE IF NOT EXISTS http_requests ( id INTEGER PRIMARY KEY, proto TEXT, url TEXT, @@ -77,7 +73,7 @@ func (c Client) prepareSchema() error { return fmt.Errorf("could not create http_requests table: %v", err) } - _, err = c.db.Exec(`CREATE TABLE IF NOT EXISTS http_responses ( + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS http_responses ( id INTEGER PRIMARY KEY, req_id INTEGER REFERENCES http_requests(id) ON DELETE CASCADE, proto TEXT, @@ -90,7 +86,7 @@ func (c Client) prepareSchema() error { return fmt.Errorf("could not create http_responses table: %v", err) } - _, err = c.db.Exec(`CREATE TABLE IF NOT EXISTS http_headers ( + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS http_headers ( id INTEGER PRIMARY KEY, req_id INTEGER REFERENCES http_requests(id) ON DELETE CASCADE, res_id INTEGER REFERENCES http_responses(id) ON DELETE CASCADE, @@ -104,9 +100,16 @@ func (c Client) prepareSchema() error { return nil } -// Close uses the underlying database. +// Close uses the underlying database if it's open. func (c *Client) Close() error { - return c.db.Close() + if c.db == nil { + return nil + } + if err := c.db.Close(); err != nil { + return fmt.Errorf("sqlite: could not close database: %v", err) + } + c.db = nil + return nil } var reqFieldToColumnMap = map[string]string{ @@ -136,6 +139,10 @@ func (c *Client) FindRequestLogs( opts reqlog.FindRequestsOptions, scope *scope.Scope, ) (reqLogs []reqlog.Request, err error) { + if c.db == nil { + return nil, reqlog.ErrNoProject + } + httpReqLogsQuery := parseHTTPRequestLogsQuery(ctx) reqQuery := sq. @@ -178,6 +185,9 @@ func (c *Client) FindRequestLogs( } func (c *Client) FindRequestLogByID(ctx context.Context, id int64) (reqlog.Request, error) { + if c.db == nil { + return reqlog.Request{}, reqlog.ErrNoProject + } httpReqLogsQuery := parseHTTPRequestLogsQuery(ctx) reqQuery := sq. @@ -218,6 +228,9 @@ func (c *Client) AddRequestLog( body []byte, timestamp time.Time, ) (*reqlog.Request, error) { + if c.db == nil { + return nil, reqlog.ErrNoProject + } reqLog := &reqlog.Request{ Request: req, @@ -289,6 +302,10 @@ func (c *Client) AddResponseLog( body []byte, timestamp time.Time, ) (*reqlog.Response, error) { + if c.db == nil { + return nil, reqlog.ErrNoProject + } + resLog := &reqlog.Response{ RequestID: reqID, Response: res, @@ -495,3 +512,7 @@ func (c *Client) queryHeaders( return nil } + +func (c *Client) IsOpen() bool { + return c.db != nil +} diff --git a/pkg/proj/proj.go b/pkg/proj/proj.go new file mode 100644 index 0000000..0b07fb2 --- /dev/null +++ b/pkg/proj/proj.go @@ -0,0 +1,135 @@ +package proj + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/dstotijn/hetty/pkg/db/sqlite" + "github.com/dstotijn/hetty/pkg/scope" +) + +// Service is used for managing projects. +type Service struct { + dbPath string + db *sqlite.Client + name string + + Scope *scope.Scope +} + +type Project struct { + Name string + IsActive bool +} + +var ( + ErrNoProject = errors.New("proj: no open project") + ErrInvalidName = errors.New("proj: invalid name, must be alphanumeric or whitespace chars") +) + +var nameRegexp = regexp.MustCompile(`^[\w\d\s]+$`) + +// NewService returns a new Service. +func NewService(dbPath string) (*Service, error) { + // Create directory for DBs if it doesn't exist yet. + if _, err := os.Stat(dbPath); os.IsNotExist(err) { + if err := os.MkdirAll(dbPath, 0755); err != nil { + return nil, fmt.Errorf("proj: could not create project directory: %v", err) + } + } + + return &Service{ + dbPath: dbPath, + db: &sqlite.Client{}, + Scope: scope.New(nil), + }, nil +} + +// Close closes the currently open project database (if there is one). +func (svc *Service) Close() error { + if err := svc.db.Close(); err != nil { + return fmt.Errorf("proj: could not close project: %v", err) + } + svc.name = "" + return nil +} + +// Delete removes a project database file from disk (if there is one). +func (svc *Service) Delete(name string) error { + if name == "" { + return errors.New("proj: name cannot be empty") + } + if svc.name == name { + return fmt.Errorf("proj: project (%v) is active", name) + } + + if err := os.Remove(filepath.Join(svc.dbPath, name+".db")); err != nil { + return fmt.Errorf("proj: could not remove database file: %v", err) + } + + return nil +} + +// Database returns the currently open database. If no database is open, it will +// return `nil`. +func (svc *Service) Database() *sqlite.Client { + return svc.db +} + +// Open opens a database identified with `name`. If a database with this +// identifier doesn't exist yet, it will be automatically created. +func (svc *Service) Open(name string) (Project, error) { + if !nameRegexp.MatchString(name) { + return Project{}, ErrInvalidName + } + if err := svc.db.Close(); err != nil { + return Project{}, fmt.Errorf("proj: could not close previously open database: %v", err) + } + + dbPath := filepath.Join(svc.dbPath, name+".db") + + err := svc.db.Open(dbPath) + if err != nil { + return Project{}, fmt.Errorf("proj: could not open database: %v", err) + } + + svc.name = name + + return Project{ + Name: name, + IsActive: true, + }, nil +} + +func (svc *Service) ActiveProject() (Project, error) { + if !svc.db.IsOpen() { + return Project{}, ErrNoProject + } + + return Project{ + Name: svc.name, + }, nil +} + +func (svc *Service) Projects() ([]Project, error) { + files, err := ioutil.ReadDir(svc.dbPath) + if err != nil { + return nil, fmt.Errorf("proj: could not read projects directory: %v", err) + } + + projects := make([]Project, len(files)) + for i, file := range files { + projName := strings.TrimSuffix(file.Name(), ".db") + projects[i] = Project{ + Name: projName, + IsActive: svc.name == projName, + } + } + + return projects, nil +} diff --git a/pkg/proxy/cert.go b/pkg/proxy/cert.go index 9798410..262cb66 100644 --- a/pkg/proxy/cert.go +++ b/pkg/proxy/cert.go @@ -83,13 +83,13 @@ func LoadOrCreateCA(caKeyFile, caCertFile string) (*x509.Certificate, *rsa.Priva keyDir, _ := filepath.Split(caKeyFile) if keyDir != "" { if _, err := os.Stat(keyDir); os.IsNotExist(err) { - os.Mkdir(keyDir, 0755) + os.MkdirAll(keyDir, 0755) } } keyDir, _ = filepath.Split(caCertFile) if keyDir != "" { if _, err := os.Stat("keyDir"); os.IsNotExist(err) { - os.Mkdir(keyDir, 0755) + os.MkdirAll(keyDir, 0755) } } diff --git a/pkg/reqlog/repo.go b/pkg/reqlog/repo.go index 36264f1..217f4cf 100644 --- a/pkg/reqlog/repo.go +++ b/pkg/reqlog/repo.go @@ -8,6 +8,10 @@ import ( "github.com/dstotijn/hetty/pkg/scope" ) +type RepositoryProvider interface { + Repository() Repository +} + type Repository interface { FindRequestLogs(ctx context.Context, opts FindRequestsOptions, scope *scope.Scope) ([]Request, error) FindRequestLogByID(ctx context.Context, id int64) (Request, error) diff --git a/pkg/reqlog/reqlog.go b/pkg/reqlog/reqlog.go index 6240c51..3232a3f 100644 --- a/pkg/reqlog/reqlog.go +++ b/pkg/reqlog/reqlog.go @@ -19,7 +19,10 @@ type contextKey int const LogBypassedKey contextKey = 0 -var ErrRequestNotFound = errors.New("reqlog: request not found") +var ( + ErrRequestNotFound = errors.New("reqlog: request not found") + ErrNoProject = errors.New("reqlog: no project") +) type Request struct { ID int64 @@ -133,6 +136,11 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM } reqLog, err := svc.addRequest(req.Context(), *clone, body, now) + if err == ErrNoProject { + ctx := context.WithValue(req.Context(), LogBypassedKey, true) + *req = *req.WithContext(ctx) + return + } if err != nil { log.Printf("[ERROR] Could not store request log: %v", err) return