fix: user profile not rendering in top navbar
This commit is contained in:
parent
800dd2014c
commit
cea99397db
@ -1,6 +1,6 @@
|
|||||||
overwrite: true
|
overwrite: true
|
||||||
schema:
|
schema:
|
||||||
- '../internal/graph/schema.graphqls'
|
- '../internal/graph/schema/*.gql'
|
||||||
documents:
|
documents:
|
||||||
- 'src/shared/graphql/*.graphqls'
|
- 'src/shared/graphql/*.graphqls'
|
||||||
- 'src/shared/graphql/**/*.ts'
|
- 'src/shared/graphql/**/*.ts'
|
||||||
|
@ -57,7 +57,7 @@ const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
|
|||||||
fetch('/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) {
|
||||||
cache.reset();
|
cache.reset();
|
||||||
@ -99,11 +99,11 @@ const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
|
|||||||
showPopup(
|
showPopup(
|
||||||
$target,
|
$target,
|
||||||
<NotificationPopup>
|
<NotificationPopup>
|
||||||
{data.notifications.map(notification => (
|
{data.notifications.map((notification) => (
|
||||||
<NotificationItem
|
<NotificationItem
|
||||||
title={notification.entity.name}
|
title={notification.notification.actionType}
|
||||||
description={`${notification.actor.name} added you as a meber to the task "${notification.entity.name}"`}
|
description={`${notification.notification.causedBy.fullname} added you as a meber to the task "${notification.notification.actionType}"`}
|
||||||
createdAt={notification.createdAt}
|
createdAt={notification.notification.createdAt}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NotificationPopup>,
|
</NotificationPopup>,
|
||||||
@ -116,7 +116,7 @@ const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
|
|||||||
// const userIsTeamOrProjectAdmin = user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID);
|
// const userIsTeamOrProjectAdmin = user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID);
|
||||||
const userIsTeamOrProjectAdmin = true;
|
const userIsTeamOrProjectAdmin = true;
|
||||||
const onInvitedMemberProfile = ($targetRef: React.RefObject<HTMLElement>, email: string) => {
|
const onInvitedMemberProfile = ($targetRef: React.RefObject<HTMLElement>, email: string) => {
|
||||||
const member = projectInvitedMembers ? projectInvitedMembers.find(u => u.email === email) : null;
|
const member = projectInvitedMembers ? projectInvitedMembers.find((u) => u.email === email) : null;
|
||||||
if (member) {
|
if (member) {
|
||||||
showPopup(
|
showPopup(
|
||||||
$targetRef,
|
$targetRef,
|
||||||
@ -144,7 +144,7 @@ const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onMemberProfile = ($targetRef: React.RefObject<HTMLElement>, memberID: string) => {
|
const onMemberProfile = ($targetRef: React.RefObject<HTMLElement>, memberID: string) => {
|
||||||
const member = projectMembers ? projectMembers.find(u => u.id === memberID) : null;
|
const member = projectMembers ? projectMembers.find((u) => u.id === memberID) : null;
|
||||||
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”.';
|
||||||
if (member) {
|
if (member) {
|
||||||
@ -153,7 +153,7 @@ const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
|
|||||||
<MiniProfile
|
<MiniProfile
|
||||||
warning={member.role && member.role.code === 'owner' ? warning : null}
|
warning={member.role && member.role.code === 'owner' ? warning : null}
|
||||||
canChangeRole={userIsTeamOrProjectAdmin}
|
canChangeRole={userIsTeamOrProjectAdmin}
|
||||||
onChangeRole={roleCode => {
|
onChangeRole={(roleCode) => {
|
||||||
if (onChangeRole) {
|
if (onChangeRole) {
|
||||||
onChangeRole(member.id, roleCode);
|
onChangeRole(member.id, roleCode);
|
||||||
}
|
}
|
||||||
@ -174,6 +174,12 @@ const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
console.log('HERE DATA');
|
||||||
|
console.log(data.me);
|
||||||
|
} else {
|
||||||
|
console.log('NO DATA');
|
||||||
|
}
|
||||||
const user = data ? data.me?.user : null;
|
const user = data ? data.me?.user : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -181,7 +187,7 @@ const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
|
|||||||
<TopNavbar
|
<TopNavbar
|
||||||
name={name}
|
name={name}
|
||||||
menuType={menuType}
|
menuType={menuType}
|
||||||
onOpenProjectFinder={$target => {
|
onOpenProjectFinder={($target) => {
|
||||||
showPopup(
|
showPopup(
|
||||||
$target,
|
$target,
|
||||||
<Popup tab={0} title={null}>
|
<Popup tab={0} title={null}>
|
||||||
|
@ -42,10 +42,6 @@ export enum ActivityType {
|
|||||||
TaskChecklistRemoved = 'TASK_CHECKLIST_REMOVED'
|
TaskChecklistRemoved = 'TASK_CHECKLIST_REMOVED'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ActorType {
|
|
||||||
User = 'USER'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AddTaskLabelInput = {
|
export type AddTaskLabelInput = {
|
||||||
taskID: Scalars['UUID'];
|
taskID: Scalars['UUID'];
|
||||||
projectLabelID: Scalars['UUID'];
|
projectLabelID: Scalars['UUID'];
|
||||||
@ -268,10 +264,6 @@ export type DuplicateTaskGroupPayload = {
|
|||||||
taskGroup: TaskGroup;
|
taskGroup: TaskGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum EntityType {
|
|
||||||
Task = 'TASK'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FindProject = {
|
export type FindProject = {
|
||||||
projectID: Scalars['UUID'];
|
projectID: Scalars['UUID'];
|
||||||
};
|
};
|
||||||
@ -791,25 +783,31 @@ export type NewUserAccount = {
|
|||||||
export type Notification = {
|
export type Notification = {
|
||||||
__typename?: 'Notification';
|
__typename?: 'Notification';
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID'];
|
||||||
entity: NotificationEntity;
|
|
||||||
actionType: ActionType;
|
actionType: ActionType;
|
||||||
actor: NotificationActor;
|
causedBy: NotificationCausedBy;
|
||||||
read: Scalars['Boolean'];
|
data: Array<NotificationData>;
|
||||||
createdAt: Scalars['Time'];
|
createdAt: Scalars['Time'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationActor = {
|
export type NotificationCausedBy = {
|
||||||
__typename?: 'NotificationActor';
|
__typename?: 'NotificationCausedBy';
|
||||||
id: Scalars['UUID'];
|
fullname: Scalars['String'];
|
||||||
type: ActorType;
|
username: Scalars['String'];
|
||||||
name: Scalars['String'];
|
id: Scalars['ID'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationEntity = {
|
export type NotificationData = {
|
||||||
__typename?: 'NotificationEntity';
|
__typename?: 'NotificationData';
|
||||||
id: Scalars['UUID'];
|
key: Scalars['String'];
|
||||||
type: EntityType;
|
value: Scalars['String'];
|
||||||
name: Scalars['String'];
|
};
|
||||||
|
|
||||||
|
export type Notified = {
|
||||||
|
__typename?: 'Notified';
|
||||||
|
id: Scalars['ID'];
|
||||||
|
notification: Notification;
|
||||||
|
read: Scalars['Boolean'];
|
||||||
|
readAt?: Maybe<Scalars['Time']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ObjectType {
|
export enum ObjectType {
|
||||||
@ -902,7 +900,7 @@ export type Query = {
|
|||||||
labelColors: Array<LabelColor>;
|
labelColors: Array<LabelColor>;
|
||||||
me?: Maybe<MePayload>;
|
me?: Maybe<MePayload>;
|
||||||
myTasks: MyTasksPayload;
|
myTasks: MyTasksPayload;
|
||||||
notifications: Array<Notification>;
|
notifications: Array<Notified>;
|
||||||
organizations: Array<Organization>;
|
organizations: Array<Organization>;
|
||||||
projects: Array<Project>;
|
projects: Array<Project>;
|
||||||
searchMembers: Array<MemberSearchResult>;
|
searchMembers: Array<MemberSearchResult>;
|
||||||
@ -995,6 +993,11 @@ export type SortTaskGroupPayload = {
|
|||||||
tasks: Array<Task>;
|
tasks: Array<Task>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Subscription = {
|
||||||
|
__typename?: 'Subscription';
|
||||||
|
notificationAdded: Notified;
|
||||||
|
};
|
||||||
|
|
||||||
export type Task = {
|
export type Task = {
|
||||||
__typename?: 'Task';
|
__typename?: 'Task';
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID'];
|
||||||
@ -2355,14 +2358,15 @@ export type TopNavbarQueryVariables = Exact<{ [key: string]: never; }>;
|
|||||||
export type TopNavbarQuery = (
|
export type TopNavbarQuery = (
|
||||||
{ __typename?: 'Query' }
|
{ __typename?: 'Query' }
|
||||||
& { notifications: Array<(
|
& { notifications: Array<(
|
||||||
|
{ __typename?: 'Notified' }
|
||||||
|
& Pick<Notified, 'id' | 'read' | 'readAt'>
|
||||||
|
& { notification: (
|
||||||
{ __typename?: 'Notification' }
|
{ __typename?: 'Notification' }
|
||||||
& Pick<Notification, 'createdAt' | 'read' | 'id' | 'actionType'>
|
& Pick<Notification, 'id' | 'actionType' | 'createdAt'>
|
||||||
& { entity: (
|
& { causedBy: (
|
||||||
{ __typename?: 'NotificationEntity' }
|
{ __typename?: 'NotificationCausedBy' }
|
||||||
& Pick<NotificationEntity, 'id' | 'type' | 'name'>
|
& Pick<NotificationCausedBy, 'username' | 'fullname' | 'id'>
|
||||||
), actor: (
|
) }
|
||||||
{ __typename?: 'NotificationActor' }
|
|
||||||
& Pick<NotificationActor, 'id' | 'type' | 'name'>
|
|
||||||
) }
|
) }
|
||||||
)>, me?: Maybe<(
|
)>, me?: Maybe<(
|
||||||
{ __typename?: 'MePayload' }
|
{ __typename?: 'MePayload' }
|
||||||
@ -4819,20 +4823,19 @@ export type ToggleTaskLabelMutationOptions = Apollo.BaseMutationOptions<ToggleTa
|
|||||||
export const TopNavbarDocument = gql`
|
export const TopNavbarDocument = gql`
|
||||||
query topNavbar {
|
query topNavbar {
|
||||||
notifications {
|
notifications {
|
||||||
createdAt
|
id
|
||||||
read
|
read
|
||||||
|
readAt
|
||||||
|
notification {
|
||||||
id
|
id
|
||||||
entity {
|
|
||||||
id
|
|
||||||
type
|
|
||||||
name
|
|
||||||
}
|
|
||||||
actor {
|
|
||||||
id
|
|
||||||
type
|
|
||||||
name
|
|
||||||
}
|
|
||||||
actionType
|
actionType
|
||||||
|
causedBy {
|
||||||
|
username
|
||||||
|
fullname
|
||||||
|
id
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
me {
|
me {
|
||||||
user {
|
user {
|
||||||
|
@ -3,20 +3,19 @@ import gql from 'graphql-tag';
|
|||||||
export const TOP_NAVBAR_QUERY = gql`
|
export const TOP_NAVBAR_QUERY = gql`
|
||||||
query topNavbar {
|
query topNavbar {
|
||||||
notifications {
|
notifications {
|
||||||
createdAt
|
id
|
||||||
read
|
read
|
||||||
|
readAt
|
||||||
|
notification {
|
||||||
id
|
id
|
||||||
entity {
|
|
||||||
id
|
|
||||||
type
|
|
||||||
name
|
|
||||||
}
|
|
||||||
actor {
|
|
||||||
id
|
|
||||||
type
|
|
||||||
name
|
|
||||||
}
|
|
||||||
actionType
|
actionType
|
||||||
|
causedBy {
|
||||||
|
username
|
||||||
|
fullname
|
||||||
|
id
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
me {
|
me {
|
||||||
user {
|
user {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/RichardKnop/machinery/v1"
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
||||||
@ -30,9 +31,13 @@ func newWebCmd() *cobra.Command {
|
|||||||
log.SetFormatter(Formatter)
|
log.SetFormatter(Formatter)
|
||||||
log.SetLevel(log.InfoLevel)
|
log.SetLevel(log.InfoLevel)
|
||||||
|
|
||||||
connection := config.GetDatabaseConnectionUri()
|
appConfig, err := config.GetAppConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
connection := appConfig.Database.GetDatabaseConnectionUri()
|
||||||
var db *sqlx.DB
|
var db *sqlx.DB
|
||||||
var err error
|
|
||||||
var retryDuration time.Duration
|
var retryDuration time.Duration
|
||||||
maxRetryNumber := 4
|
maxRetryNumber := 4
|
||||||
for i := 0; i < maxRetryNumber; i++ {
|
for i := 0; i < maxRetryNumber; i++ {
|
||||||
@ -61,11 +66,16 @@ func newWebCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appConfig, err := config.GetAppConfig()
|
var server *machinery.Server
|
||||||
|
if appConfig.Job.Enabled {
|
||||||
|
jobConfig := appConfig.Job.GetJobConfig()
|
||||||
|
server, err = machinery.NewServer(&jobConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r, _ := route.NewRouter(db, appConfig)
|
}
|
||||||
|
|
||||||
|
r, _ := route.NewRouter(db, server, appConfig)
|
||||||
log.WithFields(log.Fields{"url": viper.GetString("server.hostname")}).Info("starting server")
|
log.WithFields(log.Fields{"url": viper.GetString("server.hostname")}).Info("starting server")
|
||||||
return http.ListenAndServe(viper.GetString("server.hostname"), r)
|
return http.ListenAndServe(viper.GetString("server.hostname"), r)
|
||||||
},
|
},
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"github.com/RichardKnop/machinery/v1"
|
"github.com/RichardKnop/machinery/v1"
|
||||||
"github.com/RichardKnop/machinery/v1/config"
|
|
||||||
queueLog "github.com/RichardKnop/machinery/v1/log"
|
queueLog "github.com/RichardKnop/machinery/v1/log"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/jordanknott/taskcafe/internal/config"
|
||||||
repo "github.com/jordanknott/taskcafe/internal/db"
|
repo "github.com/jordanknott/taskcafe/internal/db"
|
||||||
"github.com/jordanknott/taskcafe/internal/notification"
|
"github.com/jordanknott/taskcafe/internal/jobs"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,13 +26,11 @@ func newWorkerCmd() *cobra.Command {
|
|||||||
log.SetFormatter(Formatter)
|
log.SetFormatter(Formatter)
|
||||||
log.SetLevel(log.InfoLevel)
|
log.SetLevel(log.InfoLevel)
|
||||||
|
|
||||||
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
|
appConfig, err := config.GetAppConfig()
|
||||||
viper.GetString("database.user"),
|
if err != nil {
|
||||||
viper.GetString("database.password"),
|
log.Panic(err)
|
||||||
viper.GetString("database.host"),
|
}
|
||||||
viper.GetString("database.name"),
|
db, err := sqlx.Connect("postgres", config.GetDatabaseConfig().GetDatabaseConnectionUri())
|
||||||
)
|
|
||||||
db, err := sqlx.Connect("postgres", connection)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
@ -43,25 +39,15 @@ func newWorkerCmd() *cobra.Command {
|
|||||||
db.SetConnMaxLifetime(5 * time.Minute)
|
db.SetConnMaxLifetime(5 * time.Minute)
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
var cnf = &config.Config{
|
|
||||||
Broker: viper.GetString("queue.broker"),
|
|
||||||
DefaultQueue: "machinery_tasks",
|
|
||||||
ResultBackend: viper.GetString("queue.store"),
|
|
||||||
AMQP: &config.AMQPConfig{
|
|
||||||
Exchange: "machinery_exchange",
|
|
||||||
ExchangeType: "direct",
|
|
||||||
BindingKey: "machinery_task",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("starting task queue server instance")
|
log.Info("starting task queue server instance")
|
||||||
server, err := machinery.NewServer(cnf)
|
jobConfig := appConfig.Job.GetJobConfig()
|
||||||
|
server, err := machinery.NewServer(&jobConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// do something with the error
|
// do something with the error
|
||||||
}
|
}
|
||||||
queueLog.Set(¬ification.MachineryLogger{})
|
queueLog.Set(&jobs.MachineryLogger{})
|
||||||
repo := *repo.NewRepository(db)
|
repo := *repo.NewRepository(db)
|
||||||
notification.RegisterTasks(server, repo)
|
jobs.RegisterTasks(server, repo)
|
||||||
|
|
||||||
worker := server.NewWorker("taskcafe_worker", 10)
|
worker := server.NewWorker("taskcafe_worker", 10)
|
||||||
log.Info("starting task queue worker")
|
log.Info("starting task queue worker")
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mConfig "github.com/RichardKnop/machinery/v1/config"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -22,8 +23,10 @@ const (
|
|||||||
SecurityTokenExpiration = "security.token_expiration"
|
SecurityTokenExpiration = "security.token_expiration"
|
||||||
SecuritySecret = "security.secret"
|
SecuritySecret = "security.secret"
|
||||||
|
|
||||||
QueueBroker = "queue.broker"
|
JobEnabled = "job.enabled"
|
||||||
QueueStore = "queue.store"
|
JobBroker = "job.broker"
|
||||||
|
JobStore = "job.store"
|
||||||
|
JobQueueName = "job.queue_name"
|
||||||
|
|
||||||
SmtpFrom = "smtp.from"
|
SmtpFrom = "smtp.from"
|
||||||
SmtpHost = "smtp.host"
|
SmtpHost = "smtp.host"
|
||||||
@ -33,7 +36,7 @@ const (
|
|||||||
SmtpSkipVerify = "false"
|
SmtpSkipVerify = "false"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaults = map[string]string{
|
var defaults = map[string]interface{}{
|
||||||
ServerHostname: "0.0.0.0:3333",
|
ServerHostname: "0.0.0.0:3333",
|
||||||
DatabaseHost: "127.0.0.1",
|
DatabaseHost: "127.0.0.1",
|
||||||
DatabaseName: "taskcafe",
|
DatabaseName: "taskcafe",
|
||||||
@ -43,14 +46,16 @@ var defaults = map[string]string{
|
|||||||
DatabaseSslMode: "disable",
|
DatabaseSslMode: "disable",
|
||||||
SecurityTokenExpiration: "15m",
|
SecurityTokenExpiration: "15m",
|
||||||
SecuritySecret: "",
|
SecuritySecret: "",
|
||||||
QueueBroker: "amqp://guest:guest@localhost:5672/",
|
JobEnabled: false,
|
||||||
QueueStore: "memcache://localhost:11211",
|
JobBroker: "amqp://guest:guest@localhost:5672/",
|
||||||
|
JobStore: "memcache://localhost:11211",
|
||||||
|
JobQueueName: "taskcafe_tasks",
|
||||||
SmtpFrom: "no-reply@example.com",
|
SmtpFrom: "no-reply@example.com",
|
||||||
SmtpHost: "localhost",
|
SmtpHost: "localhost",
|
||||||
SmtpPort: "587",
|
SmtpPort: "587",
|
||||||
SmtpUsername: "",
|
SmtpUsername: "",
|
||||||
SmtpPassword: "",
|
SmtpPassword: "",
|
||||||
SmtpSkipVerify: "false",
|
SmtpSkipVerify: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitDefaults() {
|
func InitDefaults() {
|
||||||
@ -59,21 +64,40 @@ func InitDefaults() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDatabaseConnectionUri() string {
|
|
||||||
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s port=%s sslmode=%s",
|
|
||||||
viper.GetString(DatabaseUser),
|
|
||||||
viper.GetString(DatabasePassword),
|
|
||||||
viper.GetString(DatabaseHost),
|
|
||||||
viper.GetString(DatabaseName),
|
|
||||||
viper.GetString(DatabasePort),
|
|
||||||
viper.GetString(DatabaseSslMode),
|
|
||||||
)
|
|
||||||
return connection
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppConfig struct {
|
type AppConfig struct {
|
||||||
Email EmailConfig
|
Email EmailConfig
|
||||||
Security SecurityConfig
|
Security SecurityConfig
|
||||||
|
Database DatabaseConfig
|
||||||
|
Job JobConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
Broker string
|
||||||
|
QueueName string
|
||||||
|
Store string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJobConfig() JobConfig {
|
||||||
|
return JobConfig{
|
||||||
|
Enabled: viper.GetBool(JobEnabled),
|
||||||
|
Broker: viper.GetString(JobBroker),
|
||||||
|
QueueName: viper.GetString(JobQueueName),
|
||||||
|
Store: viper.GetString(JobStore),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *JobConfig) GetJobConfig() mConfig.Config {
|
||||||
|
return mConfig.Config{
|
||||||
|
Broker: cfg.Broker,
|
||||||
|
DefaultQueue: cfg.QueueName,
|
||||||
|
ResultBackend: cfg.Store,
|
||||||
|
AMQP: &mConfig.AMQPConfig{
|
||||||
|
Exchange: "machinery_exchange",
|
||||||
|
ExchangeType: "direct",
|
||||||
|
BindingKey: "machinery_task",
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailConfig struct {
|
type EmailConfig struct {
|
||||||
@ -86,6 +110,27 @@ type EmailConfig struct {
|
|||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Name string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
SslMode string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg DatabaseConfig) GetDatabaseConnectionUri() string {
|
||||||
|
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s port=%s sslmode=%s",
|
||||||
|
cfg.Username,
|
||||||
|
cfg.Password,
|
||||||
|
cfg.Host,
|
||||||
|
cfg.Name,
|
||||||
|
cfg.Port,
|
||||||
|
cfg.SslMode,
|
||||||
|
)
|
||||||
|
return connection
|
||||||
|
}
|
||||||
|
|
||||||
type SecurityConfig struct {
|
type SecurityConfig struct {
|
||||||
AccessTokenExpiration time.Duration
|
AccessTokenExpiration time.Duration
|
||||||
Secret []byte
|
Secret []byte
|
||||||
@ -101,10 +146,14 @@ func GetAppConfig() (AppConfig, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return AppConfig{}, err
|
return AppConfig{}, err
|
||||||
}
|
}
|
||||||
|
jobCfg := GetJobConfig()
|
||||||
|
databaseCfg := GetDatabaseConfig()
|
||||||
emailCfg := GetEmailConfig()
|
emailCfg := GetEmailConfig()
|
||||||
return AppConfig{
|
return AppConfig{
|
||||||
Email: emailCfg,
|
Email: emailCfg,
|
||||||
Security: securityCfg,
|
Security: securityCfg,
|
||||||
|
Database: databaseCfg,
|
||||||
|
Job: jobCfg,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,11 +168,22 @@ func GetSecurityConfig(accessTokenExp string, secret []byte) (SecurityConfig, er
|
|||||||
|
|
||||||
func GetEmailConfig() EmailConfig {
|
func GetEmailConfig() EmailConfig {
|
||||||
return EmailConfig{
|
return EmailConfig{
|
||||||
From: viper.GetString("smtp.from"),
|
From: viper.GetString(SmtpFrom),
|
||||||
Host: viper.GetString("smtp.host"),
|
Host: viper.GetString(SmtpHost),
|
||||||
Port: viper.GetInt("smtp.port"),
|
Port: viper.GetInt(SmtpPort),
|
||||||
Username: viper.GetString("smtp.username"),
|
Username: viper.GetString(SmtpUsername),
|
||||||
Password: viper.GetString("smtp.password"),
|
Password: viper.GetString(SmtpPassword),
|
||||||
InsecureSkipVerify: viper.GetBool("smtp.skip_verify"),
|
InsecureSkipVerify: viper.GetBool(SmtpSkipVerify),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDatabaseConfig() DatabaseConfig {
|
||||||
|
return DatabaseConfig{
|
||||||
|
Username: viper.GetString(DatabaseUser),
|
||||||
|
Password: viper.GetString(DatabasePassword),
|
||||||
|
Port: viper.GetString(DatabasePort),
|
||||||
|
SslMode: viper.GetString(DatabaseSslMode),
|
||||||
|
Name: viper.GetString(DatabaseName),
|
||||||
|
Host: viper.GetString(DatabaseHost),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2047,7 +2047,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.Notified.Read(childComplexity), true
|
return e.complexity.Notified.Read(childComplexity), true
|
||||||
|
|
||||||
case "Notified.read_at":
|
case "Notified.readAt":
|
||||||
if e.complexity.Notified.ReadAt == nil {
|
if e.complexity.Notified.ReadAt == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -3185,7 +3185,7 @@ type Notified {
|
|||||||
id: ID!
|
id: ID!
|
||||||
notification: Notification!
|
notification: Notification!
|
||||||
read: Boolean!
|
read: Boolean!
|
||||||
read_at: Time
|
readAt: Time
|
||||||
}
|
}
|
||||||
|
|
||||||
`, BuiltIn: false},
|
`, BuiltIn: false},
|
||||||
@ -12388,7 +12388,7 @@ func (ec *executionContext) _Notified_read(ctx context.Context, field graphql.Co
|
|||||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Notified_read_at(ctx context.Context, field graphql.CollectedField, obj *Notified) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Notified_readAt(ctx context.Context, field graphql.CollectedField, obj *Notified) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
ec.Error(ctx, ec.Recover(ctx, r))
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
@ -21877,8 +21877,8 @@ func (ec *executionContext) _Notified(ctx context.Context, sel ast.SelectionSet,
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
invalids++
|
invalids++
|
||||||
}
|
}
|
||||||
case "read_at":
|
case "readAt":
|
||||||
out.Values[i] = ec._Notified_read_at(ctx, field, obj)
|
out.Values[i] = ec._Notified_readAt(ctx, field, obj)
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
}
|
}
|
||||||
|
@ -371,7 +371,7 @@ type Notified struct {
|
|||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Notification *db.Notification `json:"notification"`
|
Notification *db.Notification `json:"notification"`
|
||||||
Read bool `json:"read"`
|
Read bool `json:"read"`
|
||||||
ReadAt *time.Time `json:"read_at"`
|
ReadAt *time.Time `json:"readAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OwnedList struct {
|
type OwnedList struct {
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jordanknott/taskcafe/internal/db"
|
"github.com/jordanknott/taskcafe/internal/db"
|
||||||
"github.com/jordanknott/taskcafe/internal/logger"
|
"github.com/jordanknott/taskcafe/internal/logger"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *notificationResolver) ID(ctx context.Context, obj *db.Notification) (uuid.UUID, error) {
|
func (r *notificationResolver) ID(ctx context.Context, obj *db.Notification) (uuid.UUID, error) {
|
||||||
@ -23,7 +24,23 @@ func (r *notificationResolver) ActionType(ctx context.Context, obj *db.Notificat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *notificationResolver) CausedBy(ctx context.Context, obj *db.Notification) (*NotificationCausedBy, error) {
|
func (r *notificationResolver) CausedBy(ctx context.Context, obj *db.Notification) (*NotificationCausedBy, error) {
|
||||||
panic(fmt.Errorf("not implemented"))
|
user, err := r.Repository.GetUserAccountByID(ctx, obj.CausedBy)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return &NotificationCausedBy{
|
||||||
|
Fullname: "Unknown user",
|
||||||
|
Username: "unknown",
|
||||||
|
ID: obj.CausedBy,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
log.WithError(err).Error("error while resolving Notification.CausedBy")
|
||||||
|
return &NotificationCausedBy{}, err
|
||||||
|
}
|
||||||
|
return &NotificationCausedBy{
|
||||||
|
Fullname: user.FullName,
|
||||||
|
Username: user.Username,
|
||||||
|
ID: obj.CausedBy,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *notificationResolver) Data(ctx context.Context, obj *db.Notification) ([]NotificationData, error) {
|
func (r *notificationResolver) Data(ctx context.Context, obj *db.Notification) ([]NotificationData, error) {
|
||||||
|
@ -33,6 +33,6 @@ type Notified {
|
|||||||
id: ID!
|
id: ID!
|
||||||
notification: Notification!
|
notification: Notification!
|
||||||
read: Boolean!
|
read: Boolean!
|
||||||
read_at: Time
|
readAt: Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,5 +33,5 @@ type Notified {
|
|||||||
id: ID!
|
id: ID!
|
||||||
notification: Notification!
|
notification: Notification!
|
||||||
read: Boolean!
|
read: Boolean!
|
||||||
read_at: Time
|
readAt: Time
|
||||||
}
|
}
|
||||||
|
33
internal/jobs/jobs.go
Normal file
33
internal/jobs/jobs.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package jobs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/RichardKnop/machinery/v1"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jordanknott/taskcafe/internal/config"
|
||||||
|
"github.com/jordanknott/taskcafe/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterTasks(server *machinery.Server, repo db.Repository) {
|
||||||
|
tasks := JobTasks{repo}
|
||||||
|
server.RegisterTasks(map[string]interface{}{
|
||||||
|
"taskMemberWasAdded": tasks.TaskMemberWasAdded,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobTasks struct {
|
||||||
|
Repository db.Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *JobTasks) TaskMemberWasAdded(taskID, notifierID, notifiedID string) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobQueue struct {
|
||||||
|
AppConfig config.AppConfig
|
||||||
|
Server *machinery.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *JobQueue) TaskMemberWasAdded(taskID, notifier, notified uuid.UUID) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package notification
|
package jobs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
@ -1,31 +0,0 @@
|
|||||||
package notification
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/RichardKnop/machinery/v1"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jordanknott/taskcafe/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterTasks(server *machinery.Server, repo db.Repository) {
|
|
||||||
tasks := NotificationTasks{repo}
|
|
||||||
server.RegisterTasks(map[string]interface{}{
|
|
||||||
"taskMemberWasAdded": tasks.TaskMemberWasAdded,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type NotificationTasks struct {
|
|
||||||
Repository db.Repository
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *NotificationTasks) TaskMemberWasAdded(taskID, notifierID, notifiedID string) (bool, error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type NotificationQueue struct {
|
|
||||||
Server *machinery.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NotificationQueue) TaskMemberWasAdded(taskID, notifier, notified uuid.UUID) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/RichardKnop/machinery/v1"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
@ -66,7 +67,7 @@ type TaskcafeHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRouter creates a new router for chi
|
// NewRouter creates a new router for chi
|
||||||
func NewRouter(dbConnection *sqlx.DB, appConfig config.AppConfig) (chi.Router, error) {
|
func NewRouter(dbConnection *sqlx.DB, job *machinery.Server, appConfig config.AppConfig) (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
|
||||||
|
36
migrations/0067_add-user_account_settings.up.sql
Normal file
36
migrations/0067_add-user_account_settings.up.sql
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
CREATE TABLE account_setting_data_type (
|
||||||
|
data_type_id text PRIMARY KEY
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO account_setting_data_type VALUES ('string');
|
||||||
|
|
||||||
|
CREATE TABLE account_setting (
|
||||||
|
account_setting_id text PRIMARY KEY,
|
||||||
|
constrained boolean NOT NULL,
|
||||||
|
data_type text NOT NULL REFERENCES account_setting_data_type(data_type_id) ON DELETE CASCADE,
|
||||||
|
constrained_default_value text
|
||||||
|
REFERENCES account_setting_allowed_values(allowed_value_id) ON DELETE CASCADE,
|
||||||
|
unconstrained_default_value text,
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO account_setting VALUES ('email_notification_frequency', true, 'string');
|
||||||
|
|
||||||
|
CREATE TABLE account_setting_allowed_values (
|
||||||
|
allowed_value_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
setting_id int NOT NULL REFERENCES account_setting(account_setting_id) ON DELETE CASCADE,
|
||||||
|
item_value text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO account_setting_allowed_values (setting_id, item_value) VALUES (0, 'never');
|
||||||
|
INSERT INTO account_setting_allowed_values (setting_id, item_value) VALUES (0, 'hourly');
|
||||||
|
INSERT INTO account_setting_allowed_values (setting_id, item_value) VALUES (0, 'instant');
|
||||||
|
|
||||||
|
CREATE TABLE account_setting (
|
||||||
|
account_setting_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
user_id uuid NOT NULL REFERENCES user_account(user_id) ON DELETE CASCADE,
|
||||||
|
setting_id int NOT NULL REFERENCES account_setting(account_setting_id) ON DELETE CASCADE,
|
||||||
|
created_at timestamptz NOT NULL,
|
||||||
|
updated_at timestamptz NOT NULL,
|
||||||
|
allowed_value_id uuid REFERENCES account_setting_allowed_values(allowed_value_id) ON DELETE CASCADE,
|
||||||
|
unconstrained_value text
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user