feature: add web & migrate commands
This commit is contained in:
parent
1e9813601e
commit
90515f6aa4
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
uploads/*
|
uploads/*
|
||||||
!uploads/.keep
|
!uploads/.keep
|
||||||
|
|
||||||
|
internal/frontend/frontend_generated.go
|
||||||
|
citadel
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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'
|
||||||
|
@ -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",
|
||||||
|
@ -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.
|
||||||
|
@ -3,11 +3,11 @@ import Admin from 'shared/components/Admin';
|
|||||||
import Select from 'shared/components/Select';
|
import Select from 'shared/components/Select';
|
||||||
import GlobalTopNavbar from 'App/TopNavbar';
|
import GlobalTopNavbar from 'App/TopNavbar';
|
||||||
import {
|
import {
|
||||||
useUsersQuery,
|
useUsersQuery,
|
||||||
useDeleteUserAccountMutation,
|
useDeleteUserAccountMutation,
|
||||||
useCreateUserAccountMutation,
|
useCreateUserAccountMutation,
|
||||||
UsersDocument,
|
UsersDocument,
|
||||||
UsersQuery,
|
UsersQuery,
|
||||||
} from 'shared/generated/graphql';
|
} from 'shared/generated/graphql';
|
||||||
import Input from 'shared/components/Input';
|
import Input from 'shared/components/Input';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@ -33,25 +33,25 @@ const DeleteUserButton = styled(Button)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type DeleteUserPopupProps = {
|
type DeleteUserPopupProps = {
|
||||||
onDeleteUser: () => void;
|
onDeleteUser: () => void;
|
||||||
};
|
};
|
||||||
const DeleteUserPopup: React.FC<DeleteUserPopupProps> = ({ onDeleteUser }) => {
|
const DeleteUserPopup: React.FC<DeleteUserPopupProps> = ({ onDeleteUser }) => {
|
||||||
return (
|
return (
|
||||||
<DeleteUserWrapper>
|
<DeleteUserWrapper>
|
||||||
<DeleteUserDescription>Deleting this user will remove all user related data.</DeleteUserDescription>
|
<DeleteUserDescription>Deleting this user will remove all user related data.</DeleteUserDescription>
|
||||||
<DeleteUserButton onClick={() => onDeleteUser()} color="danger">
|
<DeleteUserButton onClick={() => onDeleteUser()} color="danger">
|
||||||
Delete user
|
Delete user
|
||||||
</DeleteUserButton>
|
</DeleteUserButton>
|
||||||
</DeleteUserWrapper>
|
</DeleteUserWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
type CreateUserData = {
|
type CreateUserData = {
|
||||||
email: string;
|
email: string;
|
||||||
username: string;
|
username: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
initials: string;
|
initials: string;
|
||||||
password: string;
|
password: string;
|
||||||
roleCode: string;
|
roleCode: string;
|
||||||
};
|
};
|
||||||
const CreateUserForm = styled.form`
|
const CreateUserForm = styled.form`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -73,162 +73,167 @@ const InputError = styled.span`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type AddUserPopupProps = {
|
type AddUserPopupProps = {
|
||||||
onAddUser: (user: CreateUserData) => void;
|
onAddUser: (user: CreateUserData) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
|
const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
|
||||||
const { register, handleSubmit, errors, setValue } = useForm<CreateUserData>();
|
const { register, handleSubmit, errors, setValue } = useForm<CreateUserData>();
|
||||||
const [role, setRole] = useState<string | null>(null);
|
const [role, setRole] = useState<string | null>(null);
|
||||||
register({ name: 'roleCode' }, { required: true });
|
register({ name: 'roleCode' }, { required: true });
|
||||||
|
|
||||||
const createUser = (data: CreateUserData) => {
|
const createUser = (data: CreateUserData) => {
|
||||||
onAddUser(data);
|
onAddUser(data);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<CreateUserForm onSubmit={handleSubmit(createUser)}>
|
<CreateUserForm onSubmit={handleSubmit(createUser)}>
|
||||||
<AddUserInput
|
<AddUserInput
|
||||||
floatingLabel
|
floatingLabel
|
||||||
width="100%"
|
width="100%"
|
||||||
label="Full Name"
|
label="Full Name"
|
||||||
id="fullName"
|
id="fullName"
|
||||||
name="fullName"
|
name="fullName"
|
||||||
variant="alternate"
|
variant="alternate"
|
||||||
ref={register({ required: 'Full name is required' })}
|
ref={register({ required: 'Full name is required' })}
|
||||||
/>
|
/>
|
||||||
{errors.fullName && <InputError>{errors.fullName.message}</InputError>}
|
{errors.fullName && <InputError>{errors.fullName.message}</InputError>}
|
||||||
<AddUserInput
|
<AddUserInput
|
||||||
floatingLabel
|
floatingLabel
|
||||||
width="100%"
|
width="100%"
|
||||||
label="Email"
|
label="Email"
|
||||||
id="email"
|
id="email"
|
||||||
name="email"
|
name="email"
|
||||||
variant="alternate"
|
variant="alternate"
|
||||||
ref={register({ required: 'Email is required' })}
|
ref={register({ required: 'Email is required' })}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label="Role"
|
label="Role"
|
||||||
value={role}
|
value={role}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Admin', value: 'admin' },
|
{ label: 'Admin', value: 'admin' },
|
||||||
{ label: 'Member', value: 'member' },
|
{ label: 'Member', value: 'member' },
|
||||||
]}
|
]}
|
||||||
onChange={newRole => {
|
onChange={newRole => {
|
||||||
setRole(newRole);
|
setRole(newRole);
|
||||||
setValue('roleCode', newRole.value);
|
setValue('roleCode', newRole.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{errors.email && <InputError>{errors.email.message}</InputError>}
|
{errors.email && <InputError>{errors.email.message}</InputError>}
|
||||||
<AddUserInput
|
<AddUserInput
|
||||||
floatingLabel
|
floatingLabel
|
||||||
width="100%"
|
width="100%"
|
||||||
label="Username"
|
label="Username"
|
||||||
id="username"
|
id="username"
|
||||||
name="username"
|
name="username"
|
||||||
variant="alternate"
|
variant="alternate"
|
||||||
ref={register({ required: 'Username is required' })}
|
ref={register({ required: 'Username is required' })}
|
||||||
/>
|
/>
|
||||||
{errors.username && <InputError>{errors.username.message}</InputError>}
|
{errors.username && <InputError>{errors.username.message}</InputError>}
|
||||||
<AddUserInput
|
<AddUserInput
|
||||||
floatingLabel
|
floatingLabel
|
||||||
width="100%"
|
width="100%"
|
||||||
label="Initials"
|
label="Initials"
|
||||||
id="initials"
|
id="initials"
|
||||||
name="initials"
|
name="initials"
|
||||||
variant="alternate"
|
variant="alternate"
|
||||||
ref={register({ required: 'Initials is required' })}
|
ref={register({ required: 'Initials is required' })}
|
||||||
/>
|
/>
|
||||||
{errors.initials && <InputError>{errors.initials.message}</InputError>}
|
{errors.initials && <InputError>{errors.initials.message}</InputError>}
|
||||||
<AddUserInput
|
<AddUserInput
|
||||||
floatingLabel
|
floatingLabel
|
||||||
width="100%"
|
width="100%"
|
||||||
label="Password"
|
label="Password"
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
variant="alternate"
|
variant="alternate"
|
||||||
ref={register({ required: 'Password is required' })}
|
ref={register({ required: 'Password is required' })}
|
||||||
/>
|
/>
|
||||||
{errors.password && <InputError>{errors.password.message}</InputError>}
|
{errors.password && <InputError>{errors.password.message}</InputError>}
|
||||||
<CreateUserButton type="submit">Create</CreateUserButton>
|
<CreateUserButton type="submit">Create</CreateUserButton>
|
||||||
</CreateUserForm>
|
</CreateUserForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AdminRoute = () => {
|
const AdminRoute = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = 'Citadel | Admin';
|
document.title = 'Citadel | Admin';
|
||||||
}, []);
|
}, []);
|
||||||
const { loading, data } = useUsersQuery();
|
const { loading, data } = useUsersQuery();
|
||||||
const { showPopup, hidePopup } = usePopup();
|
const { showPopup, hidePopup } = usePopup();
|
||||||
const [deleteUser] = useDeleteUserAccountMutation({
|
const [deleteUser] = useDeleteUserAccountMutation({
|
||||||
update: (client, response) => {
|
update: (client, response) => {
|
||||||
updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
|
updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
|
||||||
produce(cache, draftCache => {
|
produce(cache, draftCache => {
|
||||||
draftCache.users = cache.users.filter(u => u.id !== response.data.deleteUserAccount.userAccount.id);
|
draftCache.users = cache.users.filter(u => u.id !== response.data.deleteUserAccount.userAccount.id);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
});
|
|
||||||
const [createUser] = useCreateUserAccountMutation({
|
|
||||||
update: (client, createData) => {
|
|
||||||
const cacheData: any = client.readQuery({
|
|
||||||
query: UsersDocument,
|
|
||||||
});
|
|
||||||
console.log(cacheData);
|
|
||||||
console.log(createData);
|
|
||||||
const newData = produce(cacheData, (draftState: any) => {
|
|
||||||
draftState.users = [...draftState.users, { ...createData.data.createUserAccount }];
|
|
||||||
});
|
|
||||||
|
|
||||||
client.writeQuery({
|
|
||||||
query: UsersDocument,
|
|
||||||
data: {
|
|
||||||
...newData,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
const [createUser] = useCreateUserAccountMutation({
|
||||||
});
|
update: (client, createData) => {
|
||||||
if (loading) {
|
const cacheData: any = client.readQuery({
|
||||||
return <GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />;
|
query: UsersDocument,
|
||||||
}
|
});
|
||||||
if (data) {
|
console.log(cacheData);
|
||||||
return (
|
console.log(createData);
|
||||||
<>
|
const newData = produce(cacheData, (draftState: any) => {
|
||||||
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />
|
draftState.users = [...draftState.users, { ...createData.data.createUserAccount }];
|
||||||
<Admin
|
});
|
||||||
initialTab={1}
|
|
||||||
users={data.users}
|
client.writeQuery({
|
||||||
onInviteUser={() => {}}
|
query: UsersDocument,
|
||||||
onDeleteUser={($target, userID) => {
|
data: {
|
||||||
showPopup(
|
...newData,
|
||||||
$target,
|
},
|
||||||
<Popup tab={0} title="Delete user?" onClose={() => hidePopup()}>
|
});
|
||||||
<DeleteUserPopup
|
},
|
||||||
onDeleteUser={() => {
|
});
|
||||||
deleteUser({ variables: { userID } });
|
if (loading) {
|
||||||
hidePopup();
|
return <GlobalTopNavbar projectID={null} onSaveProjectName={() => { }} name={null} />;
|
||||||
}}
|
}
|
||||||
|
if (data) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GlobalTopNavbar projectID={null} onSaveProjectName={() => { }} name={null} />
|
||||||
|
<Admin
|
||||||
|
initialTab={1}
|
||||||
|
users={data.users}
|
||||||
|
onInviteUser={() => { }}
|
||||||
|
onUpdateUserPassword={(user, password) => {
|
||||||
|
console.log(user)
|
||||||
|
console.log(password)
|
||||||
|
hidePopup()
|
||||||
|
}}
|
||||||
|
onDeleteUser={($target, userID) => {
|
||||||
|
showPopup(
|
||||||
|
$target,
|
||||||
|
<Popup tab={0} title="Delete user?" onClose={() => hidePopup()}>
|
||||||
|
<DeleteUserPopup
|
||||||
|
onDeleteUser={() => {
|
||||||
|
deleteUser({ variables: { userID } });
|
||||||
|
hidePopup();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popup>,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onAddUser={$target => {
|
||||||
|
showPopup(
|
||||||
|
$target,
|
||||||
|
<Popup tab={0} title="Add member" onClose={() => hidePopup()}>
|
||||||
|
<AddUserPopup
|
||||||
|
onAddUser={user => {
|
||||||
|
createUser({ variables: { ...user } });
|
||||||
|
hidePopup();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popup>,
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Popup>,
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}
|
||||||
onAddUser={$target => {
|
return <span>error</span>;
|
||||||
showPopup(
|
|
||||||
$target,
|
|
||||||
<Popup tab={0} title="Add member" onClose={() => hidePopup()}>
|
|
||||||
<AddUserPopup
|
|
||||||
onAddUser={user => {
|
|
||||||
createUser({ variables: { ...user } });
|
|
||||||
hidePopup();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popup>,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <span>error</span>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AdminRoute;
|
export default AdminRoute;
|
||||||
|
@ -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);
|
||||||
|
@ -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 />
|
||||||
@ -48,10 +48,10 @@ const App = () => {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<div>loading</div>
|
<div>loading</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Routes history={history} />
|
<Routes history={history} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</PopupProvider>
|
</PopupProvider>
|
||||||
</Router>
|
</Router>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
@ -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',
|
||||||
|
@ -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';
|
||||||
|
|
||||||
@ -19,20 +19,20 @@ export const RoleCheckmark = styled(Checkmark)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const permissions = [
|
const permissions = [
|
||||||
{
|
{
|
||||||
code: 'owner',
|
code: 'owner',
|
||||||
name: 'Owner',
|
name: 'Owner',
|
||||||
description:
|
description:
|
||||||
'Can view, create and edit team projects, and change settings for the team. Will have admin rights on all projects in this team. Can delete the team and all team projects.',
|
'Can view, create and edit team projects, and change settings for the team. Will have admin rights on all projects in this team. Can delete the team and all team projects.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'admin',
|
code: 'admin',
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
description:
|
description:
|
||||||
'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;
|
||||||
@ -59,13 +59,13 @@ export const MiniProfileActionItem = styled.span<{disabled?: boolean}>`
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
${props =>
|
${props =>
|
||||||
props.disabled
|
props.disabled
|
||||||
? css`
|
? css`
|
||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
color: rgba(${props.theme.colors.text.primary}, 0.4);
|
color: rgba(${props.theme.colors.text.primary}, 0.4);
|
||||||
`
|
`
|
||||||
: css`
|
: css`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgb(115, 103, 240);
|
background: rgb(115, 103, 240);
|
||||||
@ -104,142 +104,149 @@ export const RemoveMemberButton = styled(Button)`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
type TeamRoleManagerPopupProps = {
|
type TeamRoleManagerPopupProps = {
|
||||||
user: TaskUser;
|
user: TaskUser;
|
||||||
warning?: string | null;
|
warning?: string | null;
|
||||||
canChangeRole: boolean;
|
canChangeRole: boolean;
|
||||||
onChangeRole: (roleCode: RoleCode) => void;
|
onChangeRole: (roleCode: RoleCode) => void;
|
||||||
onRemoveFromTeam?: () => void;
|
updateUserPassword?: (user: TaskUser, password: string) => void;
|
||||||
|
onRemoveFromTeam?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||||
warning,
|
warning,
|
||||||
user,
|
user,
|
||||||
canChangeRole,
|
canChangeRole,
|
||||||
onRemoveFromTeam,
|
onRemoveFromTeam,
|
||||||
onChangeRole,
|
updateUserPassword,
|
||||||
|
onChangeRole,
|
||||||
}) => {
|
}) => {
|
||||||
const {hidePopup, setTab} = usePopup();
|
const { hidePopup, setTab } = usePopup();
|
||||||
return (
|
const [userPass, setUserPass] = useState({ pass: "", passConfirm: "" });
|
||||||
<>
|
return (
|
||||||
<Popup title={null} tab={0}>
|
<>
|
||||||
<MiniProfileActions>
|
<Popup title={null} tab={0}>
|
||||||
<MiniProfileActionWrapper>
|
<MiniProfileActions>
|
||||||
{user.role && (
|
<MiniProfileActionWrapper>
|
||||||
<MiniProfileActionItem
|
{user.role && (
|
||||||
onClick={() => {
|
<MiniProfileActionItem
|
||||||
setTab(1);
|
onClick={() => {
|
||||||
}}
|
setTab(1);
|
||||||
>
|
}}
|
||||||
Change permissions...
|
>
|
||||||
|
Change permissions...
|
||||||
<CurrentPermission>{`(${user.role.name})`}</CurrentPermission>
|
<CurrentPermission>{`(${user.role.name})`}</CurrentPermission>
|
||||||
</MiniProfileActionItem>
|
</MiniProfileActionItem>
|
||||||
)}
|
)}
|
||||||
<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 && (
|
||||||
<>
|
<>
|
||||||
<Separator />
|
<Separator />
|
||||||
<WarningText>{warning}</WarningText>
|
<WarningText>{warning}</WarningText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Popup>
|
</Popup>
|
||||||
<Popup title="Change Permissions" onClose={() => hidePopup()} tab={1}>
|
<Popup title="Change Permissions" onClose={() => hidePopup()} tab={1}>
|
||||||
<MiniProfileActions>
|
<MiniProfileActions>
|
||||||
<MiniProfileActionWrapper>
|
<MiniProfileActionWrapper>
|
||||||
{permissions
|
{permissions
|
||||||
.filter(p => (user.role && user.role.code === 'owner') || p.code !== 'owner')
|
.filter(p => (user.role && user.role.code === 'owner') || p.code !== 'owner')
|
||||||
.map(perm => (
|
.map(perm => (
|
||||||
<MiniProfileActionItem
|
<MiniProfileActionItem
|
||||||
disabled={user.role && perm.code !== user.role.code && !canChangeRole}
|
disabled={user.role && perm.code !== user.role.code && !canChangeRole}
|
||||||
key={perm.code}
|
key={perm.code}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onChangeRole && user.role && perm.code !== user.role.code) {
|
if (onChangeRole && user.role && perm.code !== user.role.code) {
|
||||||
switch (perm.code) {
|
switch (perm.code) {
|
||||||
case 'owner':
|
case 'owner':
|
||||||
onChangeRole(RoleCode.Owner);
|
onChangeRole(RoleCode.Owner);
|
||||||
break;
|
break;
|
||||||
case 'admin':
|
case 'admin':
|
||||||
onChangeRole(RoleCode.Admin);
|
onChangeRole(RoleCode.Admin);
|
||||||
break;
|
break;
|
||||||
case 'member':
|
case 'member':
|
||||||
onChangeRole(RoleCode.Member);
|
onChangeRole(RoleCode.Member);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
hidePopup();
|
hidePopup();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RoleName>
|
<RoleName>
|
||||||
{perm.name}
|
{perm.name}
|
||||||
{user.role && perm.code === user.role.code && <RoleCheckmark width={12} height={12} />}
|
{user.role && perm.code === user.role.code && <RoleCheckmark width={12} height={12} />}
|
||||||
</RoleName>
|
</RoleName>
|
||||||
<RoleDescription>{perm.description}</RoleDescription>
|
<RoleDescription>{perm.description}</RoleDescription>
|
||||||
</MiniProfileActionItem>
|
</MiniProfileActionItem>
|
||||||
))}
|
))}
|
||||||
</MiniProfileActionWrapper>
|
</MiniProfileActionWrapper>
|
||||||
{user.role && user.role.code === 'owner' && (
|
{user.role && user.role.code === 'owner' && (
|
||||||
<>
|
<>
|
||||||
<Separator />
|
<Separator />
|
||||||
<WarningText>You can't change roles because there must be an owner.</WarningText>
|
<WarningText>You can't change roles because there must be an owner.</WarningText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</MiniProfileActions>
|
</MiniProfileActions>
|
||||||
</Popup>
|
</Popup>
|
||||||
<Popup title="Remove from Team?" onClose={() => hidePopup()} tab={2}>
|
<Popup title="Remove from Team?" onClose={() => hidePopup()} tab={2}>
|
||||||
<Content>
|
<Content>
|
||||||
<DeleteDescription>
|
<DeleteDescription>
|
||||||
The member will be removed from all cards on this project. They will receive a notification.
|
The member will be removed from all cards on this project. They will receive a notification.
|
||||||
</DeleteDescription>
|
</DeleteDescription>
|
||||||
<RemoveMemberButton
|
<RemoveMemberButton
|
||||||
color="danger"
|
color="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onRemoveFromTeam) {
|
if (onRemoveFromTeam) {
|
||||||
onRemoveFromTeam();
|
onRemoveFromTeam();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Remove Member
|
Remove Member
|
||||||
</RemoveMemberButton>
|
</RemoveMemberButton>
|
||||||
</Content>
|
</Content>
|
||||||
</Popup>
|
</Popup>
|
||||||
<Popup title="Reset password?" onClose={() => hidePopup()} tab={3}>
|
<Popup title="Reset password?" onClose={() => hidePopup()} tab={3}>
|
||||||
<Content>
|
<Content>
|
||||||
<DeleteDescription>
|
<DeleteDescription>
|
||||||
You can either set the user's new password directly or send the user an email allowing them to reset their own password.
|
You can either set the user's new password directly or send the user an email allowing them to reset their own password.
|
||||||
</DeleteDescription>
|
</DeleteDescription>
|
||||||
<UserPassBar>
|
<UserPassBar>
|
||||||
<UserPassButton onClick={() => setTab(4)} color="warning">Set password...</UserPassButton>
|
<UserPassButton onClick={() => setTab(4)} color="warning">Set password...</UserPassButton>
|
||||||
<UserPassButton color="warning" variant="outline">Send reset link</UserPassButton>
|
<UserPassButton color="warning" variant="outline">Send reset link</UserPassButton>
|
||||||
</UserPassBar>
|
</UserPassBar>
|
||||||
</Content>
|
</Content>
|
||||||
</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={() => {
|
||||||
</Content>
|
if (userPass.pass === userPass.passConfirm && updateUserPassword) {
|
||||||
</Popup>
|
updateUserPassword(user, userPass.pass)
|
||||||
<Popup title="Remove user" onClose={() => hidePopup()} tab={5}>
|
}
|
||||||
<Content>
|
}} color="danger">Set password</UserPassConfirmButton>
|
||||||
<DeleteDescription>
|
</Content>
|
||||||
Removing this user from the organzation will remove them from assigned tasks, projects, and teams.
|
</Popup>
|
||||||
|
<Popup title="Remove user" onClose={() => hidePopup()} tab={5}>
|
||||||
|
<Content>
|
||||||
|
<DeleteDescription>
|
||||||
|
Removing this user from the organzation will remove them from assigned tasks, projects, and teams.
|
||||||
</DeleteDescription>
|
</DeleteDescription>
|
||||||
<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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const UserSelect = styled(Select)`
|
const UserSelect = styled(Select)`
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
@ -406,7 +413,7 @@ const LockUserIcon = styled(Lock)``;
|
|||||||
const DeleteUserIcon = styled(Trash)``;
|
const DeleteUserIcon = styled(Trash)``;
|
||||||
|
|
||||||
type ActionButtonProps = {
|
type ActionButtonProps = {
|
||||||
onClick: ($target: React.RefObject<HTMLElement>) => void;
|
onClick: ($target: React.RefObject<HTMLElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionButtonWrapper = styled.div`
|
const ActionButtonWrapper = styled.div`
|
||||||
@ -415,85 +422,85 @@ 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}>
|
||||||
{children}
|
{children}
|
||||||
</ActionButtonWrapper>
|
</ActionButtonWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
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)}>
|
||||||
<DeleteUserIcon width={16} height={16} />
|
<DeleteUserIcon width={16} height={16} />
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type ListTableProps = {
|
type ListTableProps = {
|
||||||
users: Array<User>;
|
users: Array<User>;
|
||||||
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,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
|
||||||
columnDefs: [
|
|
||||||
{
|
|
||||||
minWidth: 55,
|
|
||||||
width: 55,
|
|
||||||
headerCheckboxSelection: true,
|
|
||||||
checkboxSelection: true,
|
|
||||||
},
|
|
||||||
{minWidth: 210, headerName: 'Username', editable: true, field: 'username'},
|
|
||||||
{minWidth: 225, headerName: 'Email', field: 'email'},
|
|
||||||
{minWidth: 200, headerName: 'Name', editable: true, field: 'fullName'},
|
|
||||||
{minWidth: 200, headerName: 'Role', editable: true, field: 'roleName'},
|
|
||||||
{
|
|
||||||
minWidth: 200,
|
|
||||||
headerName: 'Actions',
|
|
||||||
field: 'id',
|
|
||||||
cellRenderer: 'actionButtons',
|
|
||||||
cellRendererParams: {
|
|
||||||
onDeleteUser: (target: any, userID: any) => {
|
|
||||||
onDeleteUser(target, userID);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
columnDefs: [
|
||||||
],
|
{
|
||||||
frameworkComponents: {
|
minWidth: 55,
|
||||||
actionButtons: ActionButtons,
|
width: 55,
|
||||||
},
|
headerCheckboxSelection: true,
|
||||||
};
|
checkboxSelection: true,
|
||||||
return (
|
},
|
||||||
<Root>
|
{ minWidth: 210, headerName: 'Username', editable: true, field: 'username' },
|
||||||
<div className="ag-theme-material" style={{height: '296px', width: '100%'}}>
|
{ minWidth: 225, headerName: 'Email', field: 'email' },
|
||||||
<AgGridReact
|
{ minWidth: 200, headerName: 'Name', editable: true, field: 'fullName' },
|
||||||
rowSelection="multiple"
|
{ minWidth: 200, headerName: 'Role', editable: true, field: 'roleName' },
|
||||||
defaultColDef={data.defaultColDef}
|
{
|
||||||
columnDefs={data.columnDefs}
|
minWidth: 200,
|
||||||
rowData={users.map(u => ({...u, roleName: u.role.name}))}
|
headerName: 'Actions',
|
||||||
frameworkComponents={data.frameworkComponents}
|
field: 'id',
|
||||||
onFirstDataRendered={params => {
|
cellRenderer: 'actionButtons',
|
||||||
params.api.sizeColumnsToFit();
|
cellRendererParams: {
|
||||||
}}
|
onDeleteUser: (target: any, userID: any) => {
|
||||||
onGridSizeChanged={params => {
|
onDeleteUser(target, userID);
|
||||||
params.api.sizeColumnsToFit();
|
},
|
||||||
}}
|
},
|
||||||
/>
|
},
|
||||||
</div>
|
],
|
||||||
</Root>
|
frameworkComponents: {
|
||||||
);
|
actionButtons: ActionButtons,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
<div className="ag-theme-material" style={{ height: '296px', width: '100%' }}>
|
||||||
|
<AgGridReact
|
||||||
|
rowSelection="multiple"
|
||||||
|
defaultColDef={data.defaultColDef}
|
||||||
|
columnDefs={data.columnDefs}
|
||||||
|
rowData={users.map(u => ({ ...u, roleName: u.role.name }))}
|
||||||
|
frameworkComponents={data.frameworkComponents}
|
||||||
|
onFirstDataRendered={params => {
|
||||||
|
params.api.sizeColumnsToFit();
|
||||||
|
}}
|
||||||
|
onGridSizeChanged={params => {
|
||||||
|
params.api.sizeColumnsToFit();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
@ -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,137 +601,141 @@ 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;
|
||||||
name: string;
|
name: string;
|
||||||
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
|
||||||
key={name}
|
key={name}
|
||||||
ref={$item}
|
ref={$item}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if ($item && $item.current) {
|
if ($item && $item.current) {
|
||||||
const pos = $item.current.getBoundingClientRect();
|
const pos = $item.current.getBoundingClientRect();
|
||||||
onClick(tab, pos.top);
|
onClick(tab, pos.top);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TabNavItemButton active={active}>
|
<TabNavItemButton active={active}>
|
||||||
<User size={14} color={active ? 'rgba(115, 103, 240)' : '#c2c6dc'} />
|
<User size={14} color={active ? 'rgba(115, 103, 240)' : '#c2c6dc'} />
|
||||||
<TabNavItemSpan>{name}</TabNavItemSpan>
|
<TabNavItemSpan>{name}</TabNavItemSpan>
|
||||||
</TabNavItemButton>
|
</TabNavItemButton>
|
||||||
</TabNavItem>
|
</TabNavItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type AdminProps = {
|
type AdminProps = {
|
||||||
initialTab: number;
|
initialTab: number;
|
||||||
onAddUser: ($target: React.RefObject<HTMLElement>) => void;
|
onAddUser: ($target: React.RefObject<HTMLElement>) => void;
|
||||||
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 can’t leave because you are the only admin. To make another user an admin, click their avatar, select “Change permissions…”, and select “Admin”.';
|
'You can’t 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()
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<TabNav ref={$tabNav}>
|
<TabNav ref={$tabNav}>
|
||||||
<TabNavContent>
|
<TabNavContent>
|
||||||
{items.map((item, idx) => (
|
{items.map((item, idx) => (
|
||||||
<NavItem
|
<NavItem
|
||||||
onClick={(tab, top) => {
|
onClick={(tab, top) => {
|
||||||
if ($tabNav && $tabNav.current) {
|
if ($tabNav && $tabNav.current) {
|
||||||
const pos = $tabNav.current.getBoundingClientRect();
|
const pos = $tabNav.current.getBoundingClientRect();
|
||||||
setTab(tab);
|
setTab(tab);
|
||||||
setTop(top - pos.top);
|
setTop(top - pos.top);
|
||||||
}
|
|
||||||
}}
|
|
||||||
name={item.name}
|
|
||||||
tab={idx}
|
|
||||||
active={idx === currentTab}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<TabNavLine top={currentTop} />
|
|
||||||
</TabNavContent>
|
|
||||||
</TabNav>
|
|
||||||
<TabContentWrapper>
|
|
||||||
<TabContent>
|
|
||||||
<MemberListWrapper>
|
|
||||||
<MemberListHeader>
|
|
||||||
<ListTitle>{`Users (${users.length})`}</ListTitle>
|
|
||||||
<ListDesc>
|
|
||||||
Team members can view and join all Team Visible boards and create new boards in the team.
|
|
||||||
</ListDesc>
|
|
||||||
<ListActions>
|
|
||||||
<FilterSearch width="250px" variant="alternate" placeholder="Filter by name" />
|
|
||||||
<InviteMemberButton
|
|
||||||
onClick={$target => {
|
|
||||||
onAddUser($target);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<InviteIcon width={16} height={16} />
|
|
||||||
New Member
|
|
||||||
</InviteMemberButton>
|
|
||||||
</ListActions>
|
|
||||||
</MemberListHeader>
|
|
||||||
<MemberList>
|
|
||||||
{users.map(member => (
|
|
||||||
<MemberListItem>
|
|
||||||
<MemberProfile showRoleIcons size={32} onMemberProfile={() => {}} member={member} />
|
|
||||||
<MemberListItemDetails>
|
|
||||||
<MemberItemName>{member.fullName}</MemberItemName>
|
|
||||||
<MemberItemUsername>{`@${member.username}`}</MemberItemUsername>
|
|
||||||
</MemberListItemDetails>
|
|
||||||
<MemberItemOptions>
|
|
||||||
<MemberItemOption variant="flat">On 6 projects</MemberItemOption>
|
|
||||||
<MemberItemOption
|
|
||||||
variant="outline"
|
|
||||||
onClick={$target => {
|
|
||||||
showPopup(
|
|
||||||
$target,
|
|
||||||
<TeamRoleManagerPopup
|
|
||||||
user={member}
|
|
||||||
warning={member.role && member.role.code === 'owner' ? warning : null}
|
|
||||||
canChangeRole={member.role && member.role.code !== 'owner'}
|
|
||||||
onChangeRole={roleCode => {
|
|
||||||
updateUserRole({variables: {userID: member.id, roleCode}})
|
|
||||||
}}
|
|
||||||
onRemoveFromTeam={
|
|
||||||
member.role && member.role.code === 'owner'
|
|
||||||
? undefined
|
|
||||||
: () => {
|
|
||||||
hidePopup();
|
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
/>,
|
name={item.name}
|
||||||
);
|
tab={idx}
|
||||||
}}
|
active={idx === currentTab}
|
||||||
>
|
/>
|
||||||
Manage
|
))}
|
||||||
|
<TabNavLine top={currentTop} />
|
||||||
|
</TabNavContent>
|
||||||
|
</TabNav>
|
||||||
|
<TabContentWrapper>
|
||||||
|
<TabContent>
|
||||||
|
<MemberListWrapper>
|
||||||
|
<MemberListHeader>
|
||||||
|
<ListTitle>{`Users (${users.length})`}</ListTitle>
|
||||||
|
<ListDesc>
|
||||||
|
Team members can view and join all Team Visible boards and create new boards in the team.
|
||||||
|
</ListDesc>
|
||||||
|
<ListActions>
|
||||||
|
<FilterSearch width="250px" variant="alternate" placeholder="Filter by name" />
|
||||||
|
<InviteMemberButton
|
||||||
|
onClick={$target => {
|
||||||
|
onAddUser($target);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InviteIcon width={16} height={16} />
|
||||||
|
New Member
|
||||||
|
</InviteMemberButton>
|
||||||
|
</ListActions>
|
||||||
|
</MemberListHeader>
|
||||||
|
<MemberList>
|
||||||
|
{users.map(member => (
|
||||||
|
<MemberListItem>
|
||||||
|
<MemberProfile showRoleIcons size={32} onMemberProfile={() => { }} member={member} />
|
||||||
|
<MemberListItemDetails>
|
||||||
|
<MemberItemName>{member.fullName}</MemberItemName>
|
||||||
|
<MemberItemUsername>{`@${member.username}`}</MemberItemUsername>
|
||||||
|
</MemberListItemDetails>
|
||||||
|
<MemberItemOptions>
|
||||||
|
<MemberItemOption variant="flat">On 6 projects</MemberItemOption>
|
||||||
|
<MemberItemOption
|
||||||
|
variant="outline"
|
||||||
|
onClick={$target => {
|
||||||
|
showPopup(
|
||||||
|
$target,
|
||||||
|
<TeamRoleManagerPopup
|
||||||
|
user={member}
|
||||||
|
warning={member.role && member.role.code === 'owner' ? warning : null}
|
||||||
|
updateUserPassword={(user, password) => {
|
||||||
|
onUpdateUserPassword(user, password)
|
||||||
|
}}
|
||||||
|
canChangeRole={member.role && member.role.code !== 'owner'}
|
||||||
|
onChangeRole={roleCode => {
|
||||||
|
updateUserRole({ variables: { userID: member.id, roleCode } })
|
||||||
|
}}
|
||||||
|
onRemoveFromTeam={
|
||||||
|
member.role && member.role.code === 'owner'
|
||||||
|
? undefined
|
||||||
|
: () => {
|
||||||
|
hidePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Manage
|
||||||
</MemberItemOption>
|
</MemberItemOption>
|
||||||
</MemberItemOptions>
|
</MemberItemOptions>
|
||||||
</MemberListItem>
|
</MemberListItem>
|
||||||
))}
|
))}
|
||||||
</MemberList>
|
</MemberList>
|
||||||
</MemberListWrapper>
|
</MemberListWrapper>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</TabContentWrapper>
|
</TabContentWrapper>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Admin;
|
export default Admin;
|
||||||
|
7
go.mod
7
go.mod
@ -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
48
go.sum
@ -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=
|
||||||
|
40
internal/commands/commands.go
Normal file
40
internal/commands/commands.go
Normal 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()
|
||||||
|
}
|
68
internal/commands/migrate.go
Normal file
68
internal/commands/migrate.go
Normal 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
52
internal/commands/web.go
Normal 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
58
internal/config/config.go
Normal 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
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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 *;
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -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"`
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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!
|
||||||
|
@ -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) {
|
||||||
|
@ -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!
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CitadelHandler struct {
|
// spaHandler implements the http.Handler interface, so we can use it
|
||||||
repo db.Repository
|
// 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 NewRouter(dbConnection *sqlx.DB) (chi.Router, error) {
|
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 {
|
||||||
|
config config.AppConfig
|
||||||
|
repo db.Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
15
magefile.go
15
magefile.go
@ -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 {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user