feature: add web & migrate commands

This commit is contained in:
Jordan Knott 2020-07-15 18:20:08 -05:00
parent 1e9813601e
commit 90515f6aa4
31 changed files with 1300 additions and 640 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
node_modules node_modules
uploads/* uploads/*
!uploads/.keep !uploads/.keep
internal/frontend/frontend_generated.go
citadel

View File

@ -1,62 +1,10 @@
package main package main
import ( import (
"fmt" "github.com/jordanknott/project-citadel/api/internal/commands"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"io/ioutil"
"net/http"
"time"
"github.com/BurntSushi/toml"
"github.com/jmoiron/sqlx"
"github.com/jordanknott/project-citadel/api/internal/route"
log "github.com/sirupsen/logrus"
) )
type Database struct {
Host string
Name string
User string
Password string
}
type AppConfig struct {
Database Database
}
func main() { func main() {
dat, err := ioutil.ReadFile("conf/app.toml") commands.Execute()
if err != nil {
panic(err)
}
var appConfig AppConfig
_, err = toml.Decode(string(dat), &appConfig)
if err != nil {
panic(err)
}
Formatter := new(log.TextFormatter)
Formatter.TimestampFormat = "02-01-2006 15:04:05"
Formatter.FullTimestamp = true
log.SetFormatter(Formatter)
log.SetLevel(log.InfoLevel)
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
appConfig.Database.User,
appConfig.Database.Password,
appConfig.Database.Host,
appConfig.Database.Name,
)
db, err := sqlx.Connect("postgres", connection)
if err != nil {
log.Panic(err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
defer db.Close()
fmt.Println("starting graphql server on http://localhost:3333")
fmt.Println("starting graphql playground on http://localhost:3333/__graphql")
r, _ := route.NewRouter(db)
http.ListenAndServe(":3333", r)
} }

View File

@ -1,5 +1,24 @@
[general]
host = '0.0.0.0:3333'
[email_notifications]
enabled = true
display_name = "No Reply"
from_address = "example.com"
[storage]
storage_system = 'local_storage'
upload_dir_path = 'uploads'
[database] [database]
host = '0.0.0.0' host = '0.0.0.0'
name = 'citadel' name = 'citadel_test'
user = 'postgres' user = 'postgres'
password = 'test' password = 'test'
[smtp]
username = 'admin@example.com'
password = 'example'
server = 'mail.example.com'
port = 465
connection_security = 'STARTTLS'

View File

@ -70,6 +70,7 @@
"styled-components": "^5.0.1", "styled-components": "^5.0.1",
"typescript": "~3.7.2" "typescript": "~3.7.2"
}, },
"proxy": "http://localhost:3333",
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",

View File

@ -2,19 +2,19 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="/logo192.png" />
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
--> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.

View File

@ -188,16 +188,21 @@ const AdminRoute = () => {
}, },
}); });
if (loading) { if (loading) {
return <GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />; return <GlobalTopNavbar projectID={null} onSaveProjectName={() => { }} name={null} />;
} }
if (data) { if (data) {
return ( return (
<> <>
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} /> <GlobalTopNavbar projectID={null} onSaveProjectName={() => { }} name={null} />
<Admin <Admin
initialTab={1} initialTab={1}
users={data.users} users={data.users}
onInviteUser={() => {}} onInviteUser={() => { }}
onUpdateUserPassword={(user, password) => {
console.log(user)
console.log(password)
hidePopup()
}}
onDeleteUser={($target, userID) => { onDeleteUser={($target, userID) => {
showPopup( showPopup(
$target, $target,

View File

@ -1,9 +1,9 @@
import React, { useState, useContext, useEffect } from 'react'; import React, {useState, useContext, useEffect} from 'react';
import TopNavbar, { MenuItem } from 'shared/components/TopNavbar'; import TopNavbar, {MenuItem} from 'shared/components/TopNavbar';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import DropdownMenu, { ProfileMenu } from 'shared/components/DropdownMenu'; import DropdownMenu, {ProfileMenu} from 'shared/components/DropdownMenu';
import ProjectSettings, { DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings'; import ProjectSettings, {DeleteConfirm, DELETE_INFO} from 'shared/components/ProjectSettings';
import { useHistory } from 'react-router'; import {useHistory} from 'react-router';
import UserIDContext from 'App/context'; import UserIDContext from 'App/context';
import { import {
RoleCode, RoleCode,
@ -12,10 +12,10 @@ import {
useGetProjectsQuery, useGetProjectsQuery,
GetProjectsDocument, GetProjectsDocument,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import {usePopup, Popup} from 'shared/components/PopupMenu';
import { History } from 'history'; import {History} from 'history';
import produce from 'immer'; import produce from 'immer';
import { Link } from 'react-router-dom'; import {Link} from 'react-router-dom';
const TeamContainer = styled.div` const TeamContainer = styled.div`
display: flex; display: flex;
@ -45,7 +45,7 @@ const TeamProjectLink = styled(Link)`
user-select: none; user-select: none;
`; `;
const TeamProjectBackground = styled.div<{ color: string }>` const TeamProjectBackground = styled.div<{color: string}>`
background-image: url(null); background-image: url(null);
background-color: ${props => props.color}; background-color: ${props => props.color};
@ -68,7 +68,7 @@ const TeamProjectBackground = styled.div<{ color: string }>`
} }
`; `;
const TeamProjectAvatar = styled.div<{ color: string }>` const TeamProjectAvatar = styled.div<{color: string}>`
background-image: url(null); background-image: url(null);
background-color: ${props => props.color}; background-color: ${props => props.color};
@ -122,12 +122,12 @@ const TeamProjectContainer = styled.div`
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f']; const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
const ProjectFinder = () => { const ProjectFinder = () => {
const { loading, data } = useGetProjectsQuery(); const {loading, data} = useGetProjectsQuery();
if (loading) { if (loading) {
return <span>loading</span>; return <span>loading</span>;
} }
if (data) { if (data) {
const { projects, teams, organizations } = data; const {projects, teams, organizations} = data;
const projectTeams = teams.map(team => { const projectTeams = teams.map(team => {
return { return {
id: team.id, id: team.id,
@ -166,8 +166,8 @@ type ProjectPopupProps = {
projectID: string; projectID: string;
}; };
export const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID }) => { export const ProjectPopup: React.FC<ProjectPopupProps> = ({history, name, projectID}) => {
const { hidePopup, setTab } = usePopup(); const {hidePopup, setTab} = usePopup();
const [deleteProject] = useDeleteProjectMutation({ const [deleteProject] = useDeleteProjectMutation({
update: (client, deleteData) => { update: (client, deleteData) => {
const cacheData: any = client.readQuery({ const cacheData: any = client.readQuery({
@ -206,7 +206,7 @@ export const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, proje
deletedItems={DELETE_INFO.DELETE_PROJECTS.deletedItems} deletedItems={DELETE_INFO.DELETE_PROJECTS.deletedItems}
onConfirmDelete={() => { onConfirmDelete={() => {
if (projectID) { if (projectID) {
deleteProject({ variables: { projectID } }); deleteProject({variables: {projectID}});
hidePopup(); hidePopup();
history.push('/projects'); history.push('/projects');
} }
@ -249,16 +249,16 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
nameOnly, nameOnly,
}) => { }) => {
console.log(popupContent); console.log(popupContent);
const { loading, data } = useMeQuery(); const {loading, data} = useMeQuery();
const { showPopup, hidePopup, setTab } = usePopup(); const {showPopup, hidePopup, setTab} = usePopup();
const history = useHistory(); const history = useHistory();
const { userID, setUserID } = useContext(UserIDContext); const {userID, setUserID} = useContext(UserIDContext);
const onLogout = () => { const onLogout = () => {
fetch('http://localhost:3333/auth/logout', { fetch('/auth/logout', {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
}).then(async x => { }).then(async x => {
const { status } = x; const {status} = x;
if (status === 200) { if (status === 200) {
history.replace('/login'); history.replace('/login');
setUserID(null); setUserID(null);

View File

@ -1,16 +1,16 @@
import React, { useState, useEffect } from 'react'; import React, {useState, useEffect} from 'react';
import jwtDecode from 'jwt-decode'; import jwtDecode from 'jwt-decode';
import { createBrowserHistory } from 'history'; import {createBrowserHistory} from 'history';
import { setAccessToken } from 'shared/utils/accessToken'; import {setAccessToken} from 'shared/utils/accessToken';
import styled, { ThemeProvider } from 'styled-components'; import styled, {ThemeProvider} from 'styled-components';
import NormalizeStyles from './NormalizeStyles'; import NormalizeStyles from './NormalizeStyles';
import BaseStyles from './BaseStyles'; import BaseStyles from './BaseStyles';
import { theme } from './ThemeStyles'; import {theme} from './ThemeStyles';
import Routes from './Routes'; import Routes from './Routes';
import { UserIDContext } from './context'; import {UserIDContext} from './context';
import Navbar from './Navbar'; import Navbar from './Navbar';
import { Router } from 'react-router'; import {Router} from 'react-router';
import { PopupProvider } from 'shared/components/PopupMenu'; import {PopupProvider} from 'shared/components/PopupMenu';
const history = createBrowserHistory(); const history = createBrowserHistory();
@ -19,16 +19,16 @@ const App = () => {
const [userID, setUserID] = useState<string | null>(null); const [userID, setUserID] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
fetch('http://localhost:3333/auth/refresh_token', { fetch('/auth/refresh_token', {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
}).then(async x => { }).then(async x => {
const { status } = x; const {status} = x;
if (status === 400) { if (status === 400) {
history.replace('/login'); history.replace('/login');
} else { } else {
const response: RefreshTokenResponse = await x.json(); const response: RefreshTokenResponse = await x.json();
const { accessToken } = response; const {accessToken} = response;
const claims: JWTToken = jwtDecode(accessToken); const claims: JWTToken = jwtDecode(accessToken);
setUserID(claims.userId); setUserID(claims.userId);
setAccessToken(accessToken); setAccessToken(accessToken);
@ -39,7 +39,7 @@ const App = () => {
return ( return (
<> <>
<UserIDContext.Provider value={{ userID, setUserID }}> <UserIDContext.Provider value={{userID, setUserID}}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<NormalizeStyles /> <NormalizeStyles />
<BaseStyles /> <BaseStyles />

View File

@ -1,24 +1,24 @@
import React, { useState, useEffect, useContext } from 'react'; import React, {useState, useEffect, useContext} from 'react';
import { useForm } from 'react-hook-form'; import {useForm} from 'react-hook-form';
import { useHistory } from 'react-router'; import {useHistory} from 'react-router';
import { setAccessToken } from 'shared/utils/accessToken'; import {setAccessToken} from 'shared/utils/accessToken';
import Login from 'shared/components/Login'; import Login from 'shared/components/Login';
import { Container, LoginWrapper } from './Styles'; import {Container, LoginWrapper} from './Styles';
import UserIDContext from 'App/context'; import UserIDContext from 'App/context';
import JwtDecode from 'jwt-decode'; import JwtDecode from 'jwt-decode';
const Auth = () => { const Auth = () => {
const [invalidLoginAttempt, setInvalidLoginAttempt] = useState(0); const [invalidLoginAttempt, setInvalidLoginAttempt] = useState(0);
const history = useHistory(); const history = useHistory();
const { setUserID } = useContext(UserIDContext); const {setUserID} = useContext(UserIDContext);
const login = ( const login = (
data: LoginFormData, data: LoginFormData,
setComplete: (val: boolean) => void, setComplete: (val: boolean) => void,
setError: (field: string, eType: string, message: string) => void, setError: (field: string, eType: string, message: string) => void,
) => { ) => {
fetch('http://localhost:3333/auth/login', { fetch('/auth/login', {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@ -33,7 +33,7 @@ const Auth = () => {
setComplete(true); setComplete(true);
} else { } else {
const response = await x.json(); const response = await x.json();
const { accessToken } = response; const {accessToken} = response;
const claims: JWTToken = JwtDecode(accessToken); const claims: JWTToken = JwtDecode(accessToken);
setUserID(claims.userId); setUserID(claims.userId);
setComplete(true); setComplete(true);
@ -45,11 +45,11 @@ const Auth = () => {
}; };
useEffect(() => { useEffect(() => {
fetch('http://localhost:3333/auth/refresh_token', { fetch('/auth/refresh_token', {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
}).then(async x => { }).then(async x => {
const { status } = x; const {status} = x;
if (status === 200) { if (status === 200) {
history.replace('/projects'); history.replace('/projects');
} }

View File

@ -2,13 +2,13 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import axios from 'axios'; import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh'; import createAuthRefreshInterceptor from 'axios-auth-refresh';
import { ApolloProvider } from '@apollo/react-hooks'; import {ApolloProvider} from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client'; import {ApolloClient} from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory'; import {InMemoryCache} from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http'; import {HttpLink} from 'apollo-link-http';
import { onError } from 'apollo-link-error'; import {onError} from 'apollo-link-error';
import { ApolloLink, Observable, fromPromise } from 'apollo-link'; import {ApolloLink, Observable, fromPromise} from 'apollo-link';
import { getAccessToken, getNewToken, setAccessToken } from 'shared/utils/accessToken'; import {getAccessToken, getNewToken, setAccessToken} from 'shared/utils/accessToken';
import App from './App'; import App from './App';
// https://able.bio/AnasT/apollo-graphql-async-access-token-refresh--470t1c8 // https://able.bio/AnasT/apollo-graphql-async-access-token-refresh--470t1c8
@ -18,7 +18,7 @@ let isRefreshing = false;
let pendingRequests: any = []; let pendingRequests: any = [];
const refreshAuthLogic = (failedRequest: any) => const refreshAuthLogic = (failedRequest: any) =>
axios.post('http://localhost:3333/auth/refresh_token', {}, { withCredentials: true }).then(tokenRefreshResponse => { axios.post('/auth/refresh_token', {}, {withCredentials: true}).then(tokenRefreshResponse => {
setAccessToken(tokenRefreshResponse.data.accessToken); setAccessToken(tokenRefreshResponse.data.accessToken);
failedRequest.response.config.headers.Authorization = `Bearer ${tokenRefreshResponse.data.accessToken}`; failedRequest.response.config.headers.Authorization = `Bearer ${tokenRefreshResponse.data.accessToken}`;
return Promise.resolve(); return Promise.resolve();
@ -43,7 +43,7 @@ const setRefreshing = (newVal: boolean) => {
isRefreshing = newVal; isRefreshing = newVal;
}; };
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => { const errorLink = onError(({graphQLErrors, networkError, operation, forward}) => {
if (graphQLErrors) { if (graphQLErrors) {
for (const err of graphQLErrors) { for (const err of graphQLErrors) {
if (err.extensions && err.extensions.code) { if (err.extensions && err.extensions.code) {
@ -118,9 +118,9 @@ const requestLink = new ApolloLink(
const client = new ApolloClient({ const client = new ApolloClient({
link: ApolloLink.from([ link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => { onError(({graphQLErrors, networkError}) => {
if (graphQLErrors) { if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => graphQLErrors.forEach(({message, locations, path}) =>
console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`), console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`),
); );
} }
@ -131,7 +131,7 @@ const client = new ApolloClient({
errorLink, errorLink,
requestLink, requestLink,
new HttpLink({ new HttpLink({
uri: 'http://localhost:3333/graphql', uri: '/graphql',
credentials: 'same-origin', credentials: 'same-origin',
}), }),
]), ]),

View File

@ -1,18 +1,18 @@
import React, { useRef } from 'react'; import React, {useRef} from 'react';
import Admin from '.'; import Admin from '.';
import { theme } from 'App/ThemeStyles'; import {theme} from 'App/ThemeStyles';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles'; import BaseStyles from 'App/BaseStyles';
import { ThemeProvider } from 'styled-components'; import {ThemeProvider} from 'styled-components';
import { action } from '@storybook/addon-actions'; import {action} from '@storybook/addon-actions';
export default { export default {
component: Admin, component: Admin,
title: 'Admin', title: 'Admin',
parameters: { parameters: {
backgrounds: [ backgrounds: [
{ name: 'gray', value: '#f8f8f8', default: true }, {name: 'gray', value: '#f8f8f8', default: true},
{ name: 'white', value: '#ffffff' }, {name: 'white', value: '#ffffff'},
], ],
}, },
}; };
@ -26,13 +26,14 @@ export const Default = () => {
<Admin <Admin
onInviteUser={action('invite user')} onInviteUser={action('invite user')}
initialTab={1} initialTab={1}
onUpdateUserPassword={action('update user password')}
onDeleteUser={action('delete user')} onDeleteUser={action('delete user')}
users={[ users={[
{ {
id: '1', id: '1',
username: 'jordanthedev', username: 'jordanthedev',
email: 'jordan@jordanthedev.com', email: 'jordan@jordanthedev.com',
role: { code: 'admin', name: 'Admin' }, role: {code: 'admin', name: 'Admin'},
fullName: 'Jordan Knott', fullName: 'Jordan Knott',
profileIcon: { profileIcon: {
bgColor: '#fff', bgColor: '#fff',

View File

@ -1,12 +1,12 @@
import React, {useState, useRef} from 'react'; import React, { useState, useRef } from 'react';
import {UserPlus, Checkmark} from 'shared/icons'; import { UserPlus, Checkmark } from 'shared/icons';
import styled, {css} from 'styled-components'; import styled, { css } from 'styled-components';
import TaskAssignee from 'shared/components/TaskAssignee'; import TaskAssignee from 'shared/components/TaskAssignee';
import Select from 'shared/components/Select'; import Select from 'shared/components/Select';
import {User, Plus, Lock, Pencil, Trash} from 'shared/icons'; import { User, Plus, Lock, Pencil, Trash } from 'shared/icons';
import {usePopup, Popup} from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import {RoleCode, useUpdateUserRoleMutation} from 'shared/generated/graphql'; import { RoleCode, useUpdateUserRoleMutation } from 'shared/generated/graphql';
import {AgGridReact} from 'ag-grid-react'; import { AgGridReact } from 'ag-grid-react';
import Input from 'shared/components/Input'; import Input from 'shared/components/Input';
import Member from 'shared/components/Member'; import Member from 'shared/components/Member';
@ -32,7 +32,7 @@ const permissions = [
'Can view, create and edit team projects, and change settings for the team. Will have admin rights on all projects in this team.', 'Can view, create and edit team projects, and change settings for the team. Will have admin rights on all projects in this team.',
}, },
{code: 'member', name: 'Member', description: 'Can view, create, and edit team projects, but not change settings.'}, { code: 'member', name: 'Member', description: 'Can view, create, and edit team projects, but not change settings.' },
]; ];
export const RoleName = styled.div` export const RoleName = styled.div`
@ -50,7 +50,7 @@ export const MiniProfileActions = styled.ul`
export const MiniProfileActionWrapper = styled.li``; export const MiniProfileActionWrapper = styled.li``;
export const MiniProfileActionItem = styled.span<{disabled?: boolean}>` export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
color: #c2c6dc; color: #c2c6dc;
display: block; display: block;
font-weight: 400; font-weight: 400;
@ -108,6 +108,7 @@ type TeamRoleManagerPopupProps = {
warning?: string | null; warning?: string | null;
canChangeRole: boolean; canChangeRole: boolean;
onChangeRole: (roleCode: RoleCode) => void; onChangeRole: (roleCode: RoleCode) => void;
updateUserPassword?: (user: TaskUser, password: string) => void;
onRemoveFromTeam?: () => void; onRemoveFromTeam?: () => void;
}; };
@ -116,9 +117,11 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
user, user,
canChangeRole, canChangeRole,
onRemoveFromTeam, onRemoveFromTeam,
updateUserPassword,
onChangeRole, onChangeRole,
}) => { }) => {
const {hidePopup, setTab} = usePopup(); const { hidePopup, setTab } = usePopup();
const [userPass, setUserPass] = useState({ pass: "", passConfirm: "" });
return ( return (
<> <>
<Popup title={null} tab={0}> <Popup title={null} tab={0}>
@ -137,7 +140,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
<MiniProfileActionItem onClick={() => { <MiniProfileActionItem onClick={() => {
setTab(3) setTab(3)
}}>Reset password...</MiniProfileActionItem> }}>Reset password...</MiniProfileActionItem>
<MiniProfileActionItem onClick={() =>setTab(5)}>Remove from organzation...</MiniProfileActionItem> <MiniProfileActionItem onClick={() => setTab(5)}>Remove from organzation...</MiniProfileActionItem>
</MiniProfileActionWrapper> </MiniProfileActionWrapper>
</MiniProfileActions> </MiniProfileActions>
{warning && ( {warning && (
@ -221,9 +224,13 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
</Popup> </Popup>
<Popup title="Reset password" onClose={() => hidePopup()} tab={4}> <Popup title="Reset password" onClose={() => hidePopup()} tab={4}>
<Content> <Content>
<NewUserPassInput width="100%" variant="alternate" placeholder="New password" /> <NewUserPassInput onChange={e => setUserPass({ pass: e.currentTarget.value, passConfirm: userPass.passConfirm })} value={userPass.pass} width="100%" variant="alternate" placeholder="New password" />
<NewUserPassInput width="100%" variant="alternate" placeholder="New password (confirm)" /> <NewUserPassInput onChange={e => setUserPass({ passConfirm: e.currentTarget.value, pass: userPass.pass })} value={userPass.passConfirm} width="100%" variant="alternate" placeholder="New password (confirm)" />
<UserPassConfirmButton onClick={() => {}} color="danger">Set password</UserPassConfirmButton> <UserPassConfirmButton disabled={userPass.pass === "" || userPass.passConfirm === ""} onClick={() => {
if (userPass.pass === userPass.passConfirm && updateUserPassword) {
updateUserPassword(user, userPass.pass)
}
}} color="danger">Set password</UserPassConfirmButton>
</Content> </Content>
</Popup> </Popup>
<Popup title="Remove user" onClose={() => hidePopup()} tab={5}> <Popup title="Remove user" onClose={() => hidePopup()} tab={5}>
@ -234,8 +241,8 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
<DeleteDescription> <DeleteDescription>
The user is the owner of 3 projects & 2 teams. The user is the owner of 3 projects & 2 teams.
</DeleteDescription> </DeleteDescription>
<UserSelect onChange={() => {}} value={null} options={[{label: 'Jordan Knott', value: "jordanknott"}]} /> <UserSelect onChange={() => { }} value={null} options={[{ label: 'Jordan Knott', value: "jordanknott" }]} />
<UserPassConfirmButton onClick={() => {}} color="danger">Set password</UserPassConfirmButton> <UserPassConfirmButton onClick={() => { }} color="danger">Set password</UserPassConfirmButton>
</Content> </Content>
</Popup> </Popup>
</> </>
@ -415,7 +422,7 @@ const ActionButtonWrapper = styled.div`
display: inline-flex; display: inline-flex;
`; `;
const ActionButton: React.FC<ActionButtonProps> = ({onClick, children}) => { const ActionButton: React.FC<ActionButtonProps> = ({ onClick, children }) => {
const $wrapper = useRef<HTMLDivElement>(null); const $wrapper = useRef<HTMLDivElement>(null);
return ( return (
<ActionButtonWrapper onClick={() => onClick($wrapper)} ref={$wrapper}> <ActionButtonWrapper onClick={() => onClick($wrapper)} ref={$wrapper}>
@ -427,7 +434,7 @@ const ActionButton: React.FC<ActionButtonProps> = ({onClick, children}) => {
const ActionButtons = (params: any) => { const ActionButtons = (params: any) => {
return ( return (
<> <>
<ActionButton onClick={() => {}}> <ActionButton onClick={() => { }}>
<EditUserIcon width={16} height={16} /> <EditUserIcon width={16} height={16} />
</ActionButton> </ActionButton>
<ActionButton onClick={$target => params.onDeleteUser($target, params.value)}> <ActionButton onClick={$target => params.onDeleteUser($target, params.value)}>
@ -442,7 +449,7 @@ type ListTableProps = {
onDeleteUser: ($target: React.RefObject<HTMLElement>, userID: string) => void; onDeleteUser: ($target: React.RefObject<HTMLElement>, userID: string) => void;
}; };
const ListTable: React.FC<ListTableProps> = ({users, onDeleteUser}) => { const ListTable: React.FC<ListTableProps> = ({ users, onDeleteUser }) => {
const data = { const data = {
defaultColDef: { defaultColDef: {
resizable: true, resizable: true,
@ -455,10 +462,10 @@ const ListTable: React.FC<ListTableProps> = ({users, onDeleteUser}) => {
headerCheckboxSelection: true, headerCheckboxSelection: true,
checkboxSelection: true, checkboxSelection: true,
}, },
{minWidth: 210, headerName: 'Username', editable: true, field: 'username'}, { minWidth: 210, headerName: 'Username', editable: true, field: 'username' },
{minWidth: 225, headerName: 'Email', field: 'email'}, { minWidth: 225, headerName: 'Email', field: 'email' },
{minWidth: 200, headerName: 'Name', editable: true, field: 'fullName'}, { minWidth: 200, headerName: 'Name', editable: true, field: 'fullName' },
{minWidth: 200, headerName: 'Role', editable: true, field: 'roleName'}, { minWidth: 200, headerName: 'Role', editable: true, field: 'roleName' },
{ {
minWidth: 200, minWidth: 200,
headerName: 'Actions', headerName: 'Actions',
@ -477,12 +484,12 @@ const ListTable: React.FC<ListTableProps> = ({users, onDeleteUser}) => {
}; };
return ( return (
<Root> <Root>
<div className="ag-theme-material" style={{height: '296px', width: '100%'}}> <div className="ag-theme-material" style={{ height: '296px', width: '100%' }}>
<AgGridReact <AgGridReact
rowSelection="multiple" rowSelection="multiple"
defaultColDef={data.defaultColDef} defaultColDef={data.defaultColDef}
columnDefs={data.columnDefs} columnDefs={data.columnDefs}
rowData={users.map(u => ({...u, roleName: u.role.name}))} rowData={users.map(u => ({ ...u, roleName: u.role.name }))}
frameworkComponents={data.frameworkComponents} frameworkComponents={data.frameworkComponents}
onFirstDataRendered={params => { onFirstDataRendered={params => {
params.api.sizeColumnsToFit(); params.api.sizeColumnsToFit();
@ -534,7 +541,7 @@ const TabNavItem = styled.li`
display: block; display: block;
position: relative; position: relative;
`; `;
const TabNavItemButton = styled.button<{active: boolean}>` const TabNavItemButton = styled.button<{ active: boolean }>`
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
@ -561,7 +568,7 @@ const TabNavItemSpan = styled.span`
font-size: 14px; font-size: 14px;
`; `;
const TabNavLine = styled.span<{top: number}>` const TabNavLine = styled.span<{ top: number }>`
left: auto; left: auto;
right: 0; right: 0;
width: 2px; width: 2px;
@ -594,7 +601,7 @@ const TabContent = styled.div`
border-radius: 0.5rem; border-radius: 0.5rem;
`; `;
const items = [{name: 'Members'}, {name: 'Settings'}]; const items = [{ name: 'Members' }, { name: 'Settings' }];
type NavItemProps = { type NavItemProps = {
active: boolean; active: boolean;
@ -602,7 +609,7 @@ type NavItemProps = {
tab: number; tab: number;
onClick: (tab: number, top: number) => void; onClick: (tab: number, top: number) => void;
}; };
const NavItem: React.FC<NavItemProps> = ({active, name, tab, onClick}) => { const NavItem: React.FC<NavItemProps> = ({ active, name, tab, onClick }) => {
const $item = useRef<HTMLLIElement>(null); const $item = useRef<HTMLLIElement>(null);
return ( return (
<TabNavItem <TabNavItem
@ -629,14 +636,15 @@ type AdminProps = {
onDeleteUser: ($target: React.RefObject<HTMLElement>, userID: string) => void; onDeleteUser: ($target: React.RefObject<HTMLElement>, userID: string) => void;
onInviteUser: ($target: React.RefObject<HTMLElement>) => void; onInviteUser: ($target: React.RefObject<HTMLElement>) => void;
users: Array<User>; users: Array<User>;
onUpdateUserPassword: (user: TaskUser, password: string) => void;
}; };
const Admin: React.FC<AdminProps> = ({initialTab, onAddUser, onDeleteUser, onInviteUser, users}) => { const Admin: React.FC<AdminProps> = ({ initialTab, onAddUser, onUpdateUserPassword, onDeleteUser, onInviteUser, users }) => {
const warning = const warning =
'You cant leave because you are the only admin. To make another user an admin, click their avatar, select “Change permissions…”, and select “Admin”.'; 'You cant leave because you are the only admin. To make another user an admin, click their avatar, select “Change permissions…”, and select “Admin”.';
const [currentTop, setTop] = useState(initialTab * 48); const [currentTop, setTop] = useState(initialTab * 48);
const [currentTab, setTab] = useState(initialTab); const [currentTab, setTab] = useState(initialTab);
const {showPopup, hidePopup} = usePopup(); const { showPopup, hidePopup } = usePopup();
const $tabNav = useRef<HTMLDivElement>(null); const $tabNav = useRef<HTMLDivElement>(null);
const [updateUserRole] = useUpdateUserRoleMutation() const [updateUserRole] = useUpdateUserRoleMutation()
@ -684,7 +692,7 @@ const Admin: React.FC<AdminProps> = ({initialTab, onAddUser, onDeleteUser, onInv
<MemberList> <MemberList>
{users.map(member => ( {users.map(member => (
<MemberListItem> <MemberListItem>
<MemberProfile showRoleIcons size={32} onMemberProfile={() => {}} member={member} /> <MemberProfile showRoleIcons size={32} onMemberProfile={() => { }} member={member} />
<MemberListItemDetails> <MemberListItemDetails>
<MemberItemName>{member.fullName}</MemberItemName> <MemberItemName>{member.fullName}</MemberItemName>
<MemberItemUsername>{`@${member.username}`}</MemberItemUsername> <MemberItemUsername>{`@${member.username}`}</MemberItemUsername>
@ -699,9 +707,12 @@ const Admin: React.FC<AdminProps> = ({initialTab, onAddUser, onDeleteUser, onInv
<TeamRoleManagerPopup <TeamRoleManagerPopup
user={member} user={member}
warning={member.role && member.role.code === 'owner' ? warning : null} warning={member.role && member.role.code === 'owner' ? warning : null}
updateUserPassword={(user, password) => {
onUpdateUserPassword(user, password)
}}
canChangeRole={member.role && member.role.code !== 'owner'} canChangeRole={member.role && member.role.code !== 'owner'}
onChangeRole={roleCode => { onChangeRole={roleCode => {
updateUserRole({variables: {userID: member.id, roleCode}}) updateUserRole({ variables: { userID: member.id, roleCode } })
}} }}
onRemoveFromTeam={ onRemoveFromTeam={
member.role && member.role.code === 'owner' member.role && member.role.code === 'owner'

7
go.mod
View File

@ -11,20 +11,25 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-chi/chi v3.3.2+incompatible github.com/go-chi/chi v3.3.2+incompatible
github.com/go-chi/cors v1.0.0 github.com/go-chi/cors v1.0.0
github.com/gochrono/chrono v1.1.0
github.com/golang-migrate/migrate/v4 v4.11.0 github.com/golang-migrate/migrate/v4 v4.11.0
github.com/google/martian v2.1.0+incompatible github.com/google/martian v2.1.0+incompatible
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/jmoiron/sqlx v1.2.0 github.com/jmoiron/sqlx v1.2.0
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
github.com/lib/pq v1.3.0 github.com/lib/pq v1.3.0
github.com/magefile/mage v1.9.0 github.com/magefile/mage v1.9.0
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f // indirect github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f // indirect
github.com/pelletier/go-toml v1.8.0 github.com/pelletier/go-toml v1.8.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v1.0.0 // indirect github.com/spf13/cobra v1.0.0
github.com/spf13/jwalterweatherman v1.0.0
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.4.0
github.com/streadway/amqp v1.0.0 github.com/streadway/amqp v1.0.0
github.com/vektah/gqlparser/v2 v2.0.1 github.com/vektah/gqlparser/v2 v2.0.1
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073

48
go.sum
View File

@ -40,6 +40,8 @@ github.com/RichardKnop/machinery v1.8.6 h1:IPdPeO/yVE32isMJME2hiCgJgNibCVLjhshyj
github.com/RichardKnop/machinery v1.8.6/go.mod h1:W87mnh7t91WdrwGbdnAjvDzqD/bqBV+0+GF276gv/bU= github.com/RichardKnop/machinery v1.8.6/go.mod h1:W87mnh7t91WdrwGbdnAjvDzqD/bqBV+0+GF276gv/bU=
github.com/RichardKnop/redsync v1.2.0 h1:gK35hR3zZkQigHKm8wOGb9MpJ9BsrW6MzxezwjTcHP0= github.com/RichardKnop/redsync v1.2.0 h1:gK35hR3zZkQigHKm8wOGb9MpJ9BsrW6MzxezwjTcHP0=
github.com/RichardKnop/redsync v1.2.0/go.mod h1:9b8nBGAX3bE2uCfJGSnsDvF23mKyHTZzmvmj5FH3Tp0= github.com/RichardKnop/redsync v1.2.0/go.mod h1:9b8nBGAX3bE2uCfJGSnsDvF23mKyHTZzmvmj5FH3Tp0=
github.com/SaidinWoT/timespan v0.0.0-20160403210742-a3d8e4741124 h1:TLYKwyrCYBhTXMC9JuFFwtugk9Q2iVixqQKXfxS0TPI=
github.com/SaidinWoT/timespan v0.0.0-20160403210742-a3d8e4741124/go.mod h1:C1NXPK9z4q8t533nAdnzlEHucZsRqI66MnPwN8mWKKE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
@ -72,9 +74,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@ -94,19 +98,27 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dhui/dktest v0.3.2 h1:nZSDcnkpbotzT/nEHNsO+JCKY8i1Qoki1AYOpeLRb6M=
github.com/dhui/dktest v0.3.2/go.mod h1:l1/ib23a/CmxAe7yixtrYPc8Iy90Zy2udyaHINM5p58= github.com/dhui/dktest v0.3.2/go.mod h1:l1/ib23a/CmxAe7yixtrYPc8Iy90Zy2udyaHINM5p58=
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20200213202729-31a86c4ab209 h1:tmV+YbYOUAYDmAiamzhRKqQXaAUyUY2xVt27Rv7rCzA=
github.com/docker/docker v1.4.2-0.20200213202729-31a86c4ab209/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20200213202729-31a86c4ab209/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -153,11 +165,16 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gochrono/chrono v1.1.0 h1:6dx7rQuSGyAW92xMfN6ZNd+gX4v8a0XTAHEsJ1IDph8=
github.com/gochrono/chrono v1.1.0/go.mod h1:W225MA3JCfzO3iAW8lKJm3vrDluqe5JrmqnHkkZfcXc=
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate/v4 v4.11.0 h1:uqtd0ysK5WyBQ/T1K2uDIooJV0o2Obt6uPwP062DupQ= github.com/golang-migrate/migrate/v4 v4.11.0 h1:uqtd0ysK5WyBQ/T1K2uDIooJV0o2Obt6uPwP062DupQ=
@ -205,6 +222,9 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.1.6 h1:CisXBwYhzdPZUV+F8J4N3nzTclW78mOYz6TbPwUmhV4=
github.com/gookit/color v1.1.6/go.mod h1:655QfvFggjTrC1SaAufon2qad0RLgbdQa40lCeOdU64=
github.com/gookit/config v1.0.11/go.mod h1:+0W5UvRpZaChP3aXx0cZ+zv7U9tvX+0oSGjisJA6fWA=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
@ -228,9 +248,11 @@ github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCO
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 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= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
@ -257,6 +279,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/now v0.0.0-20180511015916-ed742868f2ae h1:8bBMcboXYVuo0WYH+rPe5mB8obO89a993hdTZ3phTjc=
github.com/jinzhu/now v0.0.0-20180511015916-ed742868f2ae/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
@ -274,6 +298,8 @@ github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0Lh
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU=
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -297,8 +323,10 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magefile/mage v1.6.2/go.mod h1:IUDi13rsHje59lecXokTfGX0QIzO45uVPlXnJYsXepA=
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE= github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
@ -326,6 +354,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f h1:0HN0GKijN4mr9SmYoW/Ni3ozuNeHiSxo2s7drhv7obY= github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f h1:0HN0GKijN4mr9SmYoW/Ni3ozuNeHiSxo2s7drhv7obY=
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
@ -337,7 +366,9 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.10.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -386,9 +417,11 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0 h1:JJV9CsgM9EC9w2iVkwuz+sMx8yRFe89PJRUrv6hPCIA=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@ -397,16 +430,21 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
@ -424,6 +462,7 @@ github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1C
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@ -433,6 +472,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/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 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/vmihailenco/msgpack v4.0.0+incompatible h1:R/ftCULcY/r0SLpalySUSd8QV4fVABi/h0D/IjlYJzg=
github.com/vmihailenco/msgpack v4.0.0+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
@ -459,6 +500,7 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
@ -501,9 +543,11 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181003013248-f5e5bdd77824/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -546,6 +590,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -626,6 +671,7 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200213224642-88e652f7a869/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200213224642-88e652f7a869/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200303165918-5bcca83a7881 h1:6bcQ/hWOMu5dXxMPcdxhx5uOoQBkeleqvbGdt4lh8hg=
golang.org/x/tools v0.0.0-20200303165918-5bcca83a7881/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200303165918-5bcca83a7881/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -644,6 +690,7 @@ google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.19.0 h1:GwFK8+l5/gdsOYKz5p6M4UK+QT8OvmHWZPJCnf+5DjA= google.golang.org/api v0.19.0 h1:GwFK8+l5/gdsOYKz5p6M4UK+QT8OvmHWZPJCnf+5DjA=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -688,6 +735,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

View File

@ -0,0 +1,40 @@
package commands
import (
"fmt"
"github.com/spf13/cobra"
)
const CitadelConfDirEnvName = "CITADEL_CONFIG_DIR"
const CitadelAppConf = "citadel"
const mainDescription = `citadel is an open soure project management
system written in Golang & React.`
var (
version = "dev"
commit = "none"
date = "unknown"
)
var versionTemplate = fmt.Sprintf(`Version: %s
Commit: %s
Built: %s`, version, commit, date+"\n")
var commandError error
var configDir string
var verbose bool
var noColor bool
var rootCmd = &cobra.Command{
Use: "citadel",
Long: mainDescription,
Version: version,
}
func Execute() {
rootCmd.SetVersionTemplate(versionTemplate)
rootCmd.AddCommand(newWebCmd(), newMigrateCmd())
rootCmd.Execute()
}

View File

@ -0,0 +1,68 @@
package commands
import (
"fmt"
"github.com/spf13/cobra"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
"github.com/jmoiron/sqlx"
"github.com/jordanknott/project-citadel/api/internal/config"
log "github.com/sirupsen/logrus"
)
type MigrateLog struct {
verbose bool
}
func (l *MigrateLog) Printf(format string, v ...interface{}) {
log.Printf("%s", v)
}
// Verbose shows if verbose print enabled
func (l *MigrateLog) Verbose() bool {
return l.verbose
}
func newMigrateCmd() *cobra.Command {
return &cobra.Command{
Use: "migrate",
Short: "Run the database schema migrations",
Long: "Run the database schema migrations",
RunE: func(cmd *cobra.Command, args []string) error {
appConfig, err := config.LoadConfig("conf/app.toml")
if err != nil {
return err
}
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
appConfig.Database.User,
appConfig.Database.Password,
appConfig.Database.Host,
appConfig.Database.Name,
)
db, err := sqlx.Connect("postgres", connection)
if err != nil {
return err
}
defer db.Close()
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
if err != nil {
return err
}
m, err := migrate.NewWithDatabaseInstance(
"file://migrations",
"postgres", driver)
if err != nil {
return err
}
logger := &MigrateLog{}
m.Log = logger
err = m.Up()
if err != nil {
return err
}
return nil
},
}
}

52
internal/commands/web.go Normal file
View File

@ -0,0 +1,52 @@
package commands
import (
"fmt"
"github.com/spf13/cobra"
"net/http"
"time"
"github.com/jmoiron/sqlx"
"github.com/jordanknott/project-citadel/api/internal/config"
"github.com/jordanknott/project-citadel/api/internal/route"
log "github.com/sirupsen/logrus"
)
func newWebCmd() *cobra.Command {
return &cobra.Command{
Use: "web",
Short: "Run the web server",
Long: "Run the web & api server",
Run: func(cmd *cobra.Command, args []string) {
appConfig, err := config.LoadConfig("conf/app.toml")
if err != nil {
log.WithError(err).Error("loading config")
}
Formatter := new(log.TextFormatter)
Formatter.TimestampFormat = "02-01-2006 15:04:05"
Formatter.FullTimestamp = true
log.SetFormatter(Formatter)
log.SetLevel(log.InfoLevel)
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
appConfig.Database.User,
appConfig.Database.Password,
appConfig.Database.Host,
appConfig.Database.Name,
)
db, err := sqlx.Connect("postgres", connection)
if err != nil {
log.Panic(err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
defer db.Close()
log.WithFields(log.Fields{"url": appConfig.General.Host}).Info("starting server")
r, _ := route.NewRouter(appConfig, db)
http.ListenAndServe(appConfig.General.Host, r)
},
}
}

58
internal/config/config.go Normal file
View File

@ -0,0 +1,58 @@
package config
import (
"github.com/BurntSushi/toml"
"io/ioutil"
)
type Database struct {
Host string
Name string
User string
Password string
}
type General struct {
Host string
}
type EmailNotifications struct {
Enabled bool
DisplayName string `toml:"display_name"`
FromAddress string `toml:"from_address"`
}
type Storage struct {
StorageSystem string `toml:"local_storage"`
UploadDirPath string `toml:"upload_dir_path"`
}
type Smtp struct {
Username string
Password string
Server string
Port int
ConnectionSecurity string `toml:"connection_security"`
}
type AppConfig struct {
General General
Database Database
EmailNotifications EmailNotifications `toml:"email_notifications"`
Storage Storage
Smtp Smtp
}
func LoadConfig(path string) (AppConfig, error) {
dat, err := ioutil.ReadFile("conf/app.toml")
if err != nil {
return AppConfig{}, err
}
var appConfig AppConfig
_, err = toml.Decode(string(dat), &appConfig)
if err != nil {
return AppConfig{}, err
}
return appConfig, nil
}

View File

@ -83,6 +83,7 @@ type Querier interface {
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error) SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error) SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
SetTeamOwner(ctx context.Context, arg SetTeamOwnerParams) (Team, error) SetTeamOwner(ctx context.Context, arg SetTeamOwnerParams) (Team, error)
SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error)
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error) UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error) UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error) UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)

View File

@ -25,3 +25,6 @@ WHERE user_id = $1;
-- name: UpdateUserRole :one -- name: UpdateUserRole :one
UPDATE user_account SET role_code = $2 WHERE user_id = $1 RETURNING *; UPDATE user_account SET role_code = $2 WHERE user_id = $1 RETURNING *;
-- name: SetUserPassword :one
UPDATE user_account SET password_hash = $2 WHERE user_id = $1 RETURNING *;

View File

@ -162,6 +162,33 @@ func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string)
return i, err return i, err
} }
const setUserPassword = `-- name: SetUserPassword :one
UPDATE user_account SET password_hash = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
`
type SetUserPasswordParams struct {
UserID uuid.UUID `json:"user_id"`
PasswordHash string `json:"password_hash"`
}
func (q *Queries) SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error) {
row := q.db.QueryRowContext(ctx, setUserPassword, arg.UserID, arg.PasswordHash)
var i UserAccount
err := row.Scan(
&i.UserID,
&i.CreatedAt,
&i.Email,
&i.Username,
&i.PasswordHash,
&i.ProfileBgColor,
&i.FullName,
&i.Initials,
&i.ProfileAvatarUrl,
&i.RoleCode,
)
return i, err
}
const updateUserAccountProfileAvatarURL = `-- name: UpdateUserAccountProfileAvatarURL :one const updateUserAccountProfileAvatarURL = `-- name: UpdateUserAccountProfileAvatarURL :one
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1 UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code

View File

@ -185,6 +185,7 @@ type ComplexityRoot struct {
UpdateTaskLocation func(childComplexity int, input NewTaskLocation) int UpdateTaskLocation func(childComplexity int, input NewTaskLocation) int
UpdateTaskName func(childComplexity int, input UpdateTaskName) int UpdateTaskName func(childComplexity int, input UpdateTaskName) int
UpdateTeamMemberRole func(childComplexity int, input UpdateTeamMemberRole) int UpdateTeamMemberRole func(childComplexity int, input UpdateTeamMemberRole) int
UpdateUserPassword func(childComplexity int, input UpdateUserPassword) int
UpdateUserRole func(childComplexity int, input UpdateUserRole) int UpdateUserRole func(childComplexity int, input UpdateUserRole) int
} }
@ -347,6 +348,11 @@ type ComplexityRoot struct {
Ok func(childComplexity int) int Ok func(childComplexity int) int
} }
UpdateUserPasswordPayload struct {
Ok func(childComplexity int) int
User func(childComplexity int) int
}
UpdateUserRolePayload struct { UpdateUserRolePayload struct {
User func(childComplexity int) int User func(childComplexity int) int
} }
@ -415,6 +421,7 @@ type MutationResolver interface {
DeleteUserAccount(ctx context.Context, input DeleteUserAccount) (*DeleteUserAccountPayload, error) DeleteUserAccount(ctx context.Context, input DeleteUserAccount) (*DeleteUserAccountPayload, error)
LogoutUser(ctx context.Context, input LogoutUser) (bool, error) LogoutUser(ctx context.Context, input LogoutUser) (bool, error)
ClearProfileAvatar(ctx context.Context) (*db.UserAccount, error) ClearProfileAvatar(ctx context.Context) (*db.UserAccount, error)
UpdateUserPassword(ctx context.Context, input UpdateUserPassword) (*UpdateUserPasswordPayload, error)
UpdateUserRole(ctx context.Context, input UpdateUserRole) (*UpdateUserRolePayload, error) UpdateUserRole(ctx context.Context, input UpdateUserRole) (*UpdateUserRolePayload, error)
} }
type OrganizationResolver interface { type OrganizationResolver interface {
@ -1341,6 +1348,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.UpdateTeamMemberRole(childComplexity, args["input"].(UpdateTeamMemberRole)), true return e.complexity.Mutation.UpdateTeamMemberRole(childComplexity, args["input"].(UpdateTeamMemberRole)), true
case "Mutation.updateUserPassword":
if e.complexity.Mutation.UpdateUserPassword == nil {
break
}
args, err := ec.field_Mutation_updateUserPassword_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.UpdateUserPassword(childComplexity, args["input"].(UpdateUserPassword)), true
case "Mutation.updateUserRole": case "Mutation.updateUserRole":
if e.complexity.Mutation.UpdateUserRole == nil { if e.complexity.Mutation.UpdateUserRole == nil {
break break
@ -2008,6 +2027,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.UpdateTeamMemberRolePayload.Ok(childComplexity), true return e.complexity.UpdateTeamMemberRolePayload.Ok(childComplexity), true
case "UpdateUserPasswordPayload.ok":
if e.complexity.UpdateUserPasswordPayload.Ok == nil {
break
}
return e.complexity.UpdateUserPasswordPayload.Ok(childComplexity), true
case "UpdateUserPasswordPayload.user":
if e.complexity.UpdateUserPasswordPayload.User == nil {
break
}
return e.complexity.UpdateUserPasswordPayload.User(childComplexity), true
case "UpdateUserRolePayload.user": case "UpdateUserRolePayload.user":
if e.complexity.UpdateUserRolePayload.User == nil { if e.complexity.UpdateUserRolePayload.User == nil {
break break
@ -2707,9 +2740,20 @@ extend type Mutation {
logoutUser(input: LogoutUser!): Boolean! logoutUser(input: LogoutUser!): Boolean!
clearProfileAvatar: UserAccount! clearProfileAvatar: UserAccount!
updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload! updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
} }
input UpdateUserPassword {
userID: UUID!
password: String!
}
type UpdateUserPasswordPayload {
ok: Boolean!
user: UserAccount!
}
input UpdateUserRole { input UpdateUserRole {
userID: UUID! userID: UUID!
roleCode: RoleCode! roleCode: RoleCode!
@ -3411,6 +3455,20 @@ func (ec *executionContext) field_Mutation_updateTeamMemberRole_args(ctx context
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_updateUserPassword_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 UpdateUserPassword
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalNUpdateUserPassword2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPassword(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_updateUserRole_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_updateUserRole_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -6761,6 +6819,47 @@ func (ec *executionContext) _Mutation_clearProfileAvatar(ctx context.Context, fi
return ec.marshalNUserAccount2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐUserAccount(ctx, field.Selections, res) return ec.marshalNUserAccount2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐUserAccount(ctx, field.Selections, res)
} }
func (ec *executionContext) _Mutation_updateUserPassword(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,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_updateUserPassword_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().UpdateUserPassword(rctx, args["input"].(UpdateUserPassword))
})
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.(*UpdateUserPasswordPayload)
fc.Result = res
return ec.marshalNUpdateUserPasswordPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPasswordPayload(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_updateUserRole(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_updateUserRole(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -9945,6 +10044,74 @@ func (ec *executionContext) _UpdateTeamMemberRolePayload_member(ctx context.Cont
return ec.marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐMember(ctx, field.Selections, res) return ec.marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐMember(ctx, field.Selections, res)
} }
func (ec *executionContext) _UpdateUserPasswordPayload_ok(ctx context.Context, field graphql.CollectedField, obj *UpdateUserPasswordPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "UpdateUserPasswordPayload",
Field: field,
Args: nil,
IsMethod: 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.Ok, 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) _UpdateUserPasswordPayload_user(ctx context.Context, field graphql.CollectedField, obj *UpdateUserPasswordPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "UpdateUserPasswordPayload",
Field: field,
Args: nil,
IsMethod: 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.User, 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.(*db.UserAccount)
fc.Result = res
return ec.marshalNUserAccount2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐUserAccount(ctx, field.Selections, res)
}
func (ec *executionContext) _UpdateUserRolePayload_user(ctx context.Context, field graphql.CollectedField, obj *UpdateUserRolePayload) (ret graphql.Marshaler) { func (ec *executionContext) _UpdateUserRolePayload_user(ctx context.Context, field graphql.CollectedField, obj *UpdateUserRolePayload) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -12554,6 +12721,30 @@ func (ec *executionContext) unmarshalInputUpdateTeamMemberRole(ctx context.Conte
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputUpdateUserPassword(ctx context.Context, obj interface{}) (UpdateUserPassword, error) {
var it UpdateUserPassword
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "userID":
var err error
it.UserID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
case "password":
var err error
it.Password, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputUpdateUserRole(ctx context.Context, obj interface{}) (UpdateUserRole, error) { func (ec *executionContext) unmarshalInputUpdateUserRole(ctx context.Context, obj interface{}) (UpdateUserRole, error) {
var it UpdateUserRole var it UpdateUserRole
var asMap = obj.(map[string]interface{}) var asMap = obj.(map[string]interface{})
@ -13340,6 +13531,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "updateUserPassword":
out.Values[i] = ec._Mutation_updateUserPassword(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "updateUserRole": case "updateUserRole":
out.Values[i] = ec._Mutation_updateUserRole(ctx, field) out.Values[i] = ec._Mutation_updateUserRole(ctx, field)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@ -14668,6 +14864,38 @@ func (ec *executionContext) _UpdateTeamMemberRolePayload(ctx context.Context, se
return out return out
} }
var updateUserPasswordPayloadImplementors = []string{"UpdateUserPasswordPayload"}
func (ec *executionContext) _UpdateUserPasswordPayload(ctx context.Context, sel ast.SelectionSet, obj *UpdateUserPasswordPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, updateUserPasswordPayloadImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("UpdateUserPasswordPayload")
case "ok":
out.Values[i] = ec._UpdateUserPasswordPayload_ok(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "user":
out.Values[i] = ec._UpdateUserPasswordPayload_user(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 updateUserRolePayloadImplementors = []string{"UpdateUserRolePayload"} var updateUserRolePayloadImplementors = []string{"UpdateUserRolePayload"}
func (ec *executionContext) _UpdateUserRolePayload(ctx context.Context, sel ast.SelectionSet, obj *UpdateUserRolePayload) graphql.Marshaler { func (ec *executionContext) _UpdateUserRolePayload(ctx context.Context, sel ast.SelectionSet, obj *UpdateUserRolePayload) graphql.Marshaler {
@ -16230,6 +16458,24 @@ func (ec *executionContext) marshalNUpdateTeamMemberRolePayload2ᚖgithubᚗcom
return ec._UpdateTeamMemberRolePayload(ctx, sel, v) return ec._UpdateTeamMemberRolePayload(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNUpdateUserPassword2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPassword(ctx context.Context, v interface{}) (UpdateUserPassword, error) {
return ec.unmarshalInputUpdateUserPassword(ctx, v)
}
func (ec *executionContext) marshalNUpdateUserPasswordPayload2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPasswordPayload(ctx context.Context, sel ast.SelectionSet, v UpdateUserPasswordPayload) graphql.Marshaler {
return ec._UpdateUserPasswordPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNUpdateUserPasswordPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserPasswordPayload(ctx context.Context, sel ast.SelectionSet, v *UpdateUserPasswordPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._UpdateUserPasswordPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNUpdateUserRole2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserRole(ctx context.Context, v interface{}) (UpdateUserRole, error) { func (ec *executionContext) unmarshalNUpdateUserRole2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐUpdateUserRole(ctx context.Context, v interface{}) (UpdateUserRole, error) {
return ec.unmarshalInputUpdateUserRole(ctx, v) return ec.unmarshalInputUpdateUserRole(ctx, v)
} }

View File

@ -12,13 +12,15 @@ import (
"github.com/99designs/gqlgen/graphql/handler/transport" "github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground" "github.com/99designs/gqlgen/graphql/playground"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jordanknott/project-citadel/api/internal/config"
"github.com/jordanknott/project-citadel/api/internal/db" "github.com/jordanknott/project-citadel/api/internal/db"
) )
// NewHandler returns a new graphql endpoint handler. // NewHandler returns a new graphql endpoint handler.
func NewHandler(repo db.Repository) http.Handler { func NewHandler(config config.AppConfig, repo db.Repository) http.Handler {
srv := handler.New(NewExecutableSchema(Config{ srv := handler.New(NewExecutableSchema(Config{
Resolvers: &Resolver{ Resolvers: &Resolver{
Config: config,
Repository: repo, Repository: repo,
}, },
})) }))

View File

@ -401,6 +401,16 @@ type UpdateTeamMemberRolePayload struct {
Member *Member `json:"member"` Member *Member `json:"member"`
} }
type UpdateUserPassword struct {
UserID uuid.UUID `json:"userID"`
Password string `json:"password"`
}
type UpdateUserPasswordPayload struct {
Ok bool `json:"ok"`
User *db.UserAccount `json:"user"`
}
type UpdateUserRole struct { type UpdateUserRole struct {
UserID uuid.UUID `json:"userID"` UserID uuid.UUID `json:"userID"`
RoleCode RoleCode `json:"roleCode"` RoleCode RoleCode `json:"roleCode"`

View File

@ -5,10 +5,12 @@ package graph
import ( import (
"sync" "sync"
"github.com/jordanknott/project-citadel/api/internal/config"
"github.com/jordanknott/project-citadel/api/internal/db" "github.com/jordanknott/project-citadel/api/internal/db"
) )
type Resolver struct { type Resolver struct {
Config config.AppConfig
Repository db.Repository Repository db.Repository
mu sync.Mutex mu sync.Mutex
} }

View File

@ -570,9 +570,20 @@ extend type Mutation {
logoutUser(input: LogoutUser!): Boolean! logoutUser(input: LogoutUser!): Boolean!
clearProfileAvatar: UserAccount! clearProfileAvatar: UserAccount!
updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload! updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
} }
input UpdateUserPassword {
userID: UUID!
password: String!
}
type UpdateUserPasswordPayload {
ok: Boolean!
user: UserAccount!
}
input UpdateUserRole { input UpdateUserRole {
userID: UUID! userID: UUID!
roleCode: RoleCode! roleCode: RoleCode!

View File

@ -783,13 +783,24 @@ func (r *mutationResolver) ClearProfileAvatar(ctx context.Context) (*db.UserAcco
return &user, nil return &user, nil
} }
func (r *mutationResolver) UpdateUserPassword(ctx context.Context, input UpdateUserPassword) (*UpdateUserPasswordPayload, error) {
hashedPwd, err := bcrypt.GenerateFromPassword([]byte(input.Password), 14)
if err != nil {
return &UpdateUserPasswordPayload{}, err
}
user, err := r.Repository.SetUserPassword(ctx, db.SetUserPasswordParams{UserID: input.UserID, PasswordHash: string(hashedPwd)})
if err != nil {
return &UpdateUserPasswordPayload{}, err
}
return &UpdateUserPasswordPayload{Ok: true, User: &user}, err
}
func (r *mutationResolver) UpdateUserRole(ctx context.Context, input UpdateUserRole) (*UpdateUserRolePayload, error) { func (r *mutationResolver) UpdateUserRole(ctx context.Context, input UpdateUserRole) (*UpdateUserRolePayload, error) {
user, err := r.Repository.UpdateUserRole(ctx, db.UpdateUserRoleParams{RoleCode: input.RoleCode.String(), UserID: input.UserID}) user, err := r.Repository.UpdateUserRole(ctx, db.UpdateUserRoleParams{RoleCode: input.RoleCode.String(), UserID: input.UserID})
if err != nil { if err != nil {
return &UpdateUserRolePayload{}, err return &UpdateUserRolePayload{}, err
} }
return &UpdateUserRolePayload{User: &user}, nil return &UpdateUserRolePayload{User: &user}, nil
} }
func (r *organizationResolver) ID(ctx context.Context, obj *db.Organization) (uuid.UUID, error) { func (r *organizationResolver) ID(ctx context.Context, obj *db.Organization) (uuid.UUID, error) {

View File

@ -5,9 +5,20 @@ extend type Mutation {
logoutUser(input: LogoutUser!): Boolean! logoutUser(input: LogoutUser!): Boolean!
clearProfileAvatar: UserAccount! clearProfileAvatar: UserAccount!
updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload! updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
} }
input UpdateUserPassword {
userID: UUID!
password: String!
}
type UpdateUserPasswordPayload {
ok: Boolean!
user: UserAccount!
}
input UpdateUserRole { input UpdateUserRole {
userID: UUID! userID: UUID!
roleCode: RoleCode! roleCode: RoleCode!

View File

@ -5,13 +5,27 @@ import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os"
"github.com/google/uuid" "github.com/google/uuid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"time"
"github.com/jordanknott/project-citadel/api/internal/db" "github.com/jordanknott/project-citadel/api/internal/db"
"github.com/jordanknott/project-citadel/api/internal/frontend"
) )
func (h *CitadelHandler) Frontend(w http.ResponseWriter, r *http.Request) {
f, err := frontend.Frontend.Open("index.h")
if os.IsNotExist(err) {
log.Warning("does not exist")
} else if err != nil {
log.WithError(err).Error("frontend")
}
http.ServeContent(w, r, "index.html", time.Now(), f)
}
func (h *CitadelHandler) ProfileImageUpload(w http.ResponseWriter, r *http.Request) { func (h *CitadelHandler) ProfileImageUpload(w http.ResponseWriter, r *http.Request) {
log.Info("preparing to upload file") log.Info("preparing to upload file")
userID, ok := r.Context().Value("userID").(uuid.UUID) userID, ok := r.Context().Value("userID").(uuid.UUID)

View File

@ -10,16 +10,61 @@ import (
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/jordanknott/project-citadel/api/internal/config"
"github.com/jordanknott/project-citadel/api/internal/db" "github.com/jordanknott/project-citadel/api/internal/db"
"github.com/jordanknott/project-citadel/api/internal/frontend"
"github.com/jordanknott/project-citadel/api/internal/graph" "github.com/jordanknott/project-citadel/api/internal/graph"
"github.com/jordanknott/project-citadel/api/internal/logger" "github.com/jordanknott/project-citadel/api/internal/logger"
"os"
"path/filepath"
) )
// spaHandler implements the http.Handler interface, so we can use it
// to respond to HTTP requests. The path to the static directory and
// path to the index file within that static directory are used to
// serve the SPA in the given static directory.
type FrontendHandler struct {
staticPath string
indexPath string
}
func IsDir(f http.File) bool {
fi, err := f.Stat()
if err != nil {
return false
}
return fi.IsDir()
}
func (h FrontendHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path, err := filepath.Abs(r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
f, err := frontend.Frontend.Open(path)
if os.IsNotExist(err) || IsDir(f) {
index, err := frontend.Frontend.Open("index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.ServeContent(w, r, "index.html", time.Now(), index)
return
} else if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.ServeContent(w, r, path, time.Now(), f)
}
type CitadelHandler struct { type CitadelHandler struct {
config config.AppConfig
repo db.Repository repo db.Repository
} }
func NewRouter(dbConnection *sqlx.DB) (chi.Router, error) { func NewRouter(config config.AppConfig, dbConnection *sqlx.DB) (chi.Router, error) {
formatter := new(log.TextFormatter) formatter := new(log.TextFormatter)
formatter.TimestampFormat = "02-01-2006 15:04:05" formatter.TimestampFormat = "02-01-2006 15:04:05"
formatter.FullTimestamp = true formatter.FullTimestamp = true
@ -47,7 +92,7 @@ func NewRouter(dbConnection *sqlx.DB) (chi.Router, error) {
r.Use(middleware.Timeout(60 * time.Second)) r.Use(middleware.Timeout(60 * time.Second))
repository := db.NewRepository(dbConnection) repository := db.NewRepository(dbConnection)
citadelHandler := CitadelHandler{*repository} citadelHandler := CitadelHandler{config, *repository}
var imgServer = http.FileServer(http.Dir("./uploads/")) var imgServer = http.FileServer(http.Dir("./uploads/"))
r.Group(func(mux chi.Router) { r.Group(func(mux chi.Router) {
@ -59,8 +104,11 @@ func NewRouter(dbConnection *sqlx.DB) (chi.Router, error) {
r.Group(func(mux chi.Router) { r.Group(func(mux chi.Router) {
mux.Use(AuthenticationMiddleware) mux.Use(AuthenticationMiddleware)
mux.Post("/users/me/avatar", citadelHandler.ProfileImageUpload) mux.Post("/users/me/avatar", citadelHandler.ProfileImageUpload)
mux.Handle("/graphql", graph.NewHandler(*repository)) mux.Handle("/graphql", graph.NewHandler(config, *repository))
}) })
frontend := FrontendHandler{staticPath: "build", indexPath: "index.html"}
r.Handle("/*", frontend)
return r, nil return r, nil
} }

View File

@ -5,7 +5,9 @@ package main
import ( import (
"fmt" "fmt"
"github.com/magefile/mage/sh" "github.com/magefile/mage/sh"
"github.com/shurcooL/vfsgen"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"strings" "strings"
) )
@ -14,6 +16,19 @@ var Aliases = map[string]interface{}{
"g": Generate, "g": Generate,
} }
func Vfs() error {
var fs http.FileSystem = http.Dir("frontend/build")
err := vfsgen.Generate(fs, vfsgen.Options{
Filename: "internal/frontend/frontend_generated.go",
PackageName: "frontend",
VariableName: "Frontend",
})
if err != nil {
panic(err)
}
return nil
}
// Runs go mod download and then installs the binary. // Runs go mod download and then installs the binary.
func Generate() error { func Generate() error {