fix: user profile not rendering in top navbar

This commit is contained in:
Jordan Knott 2021-10-30 17:20:41 -05:00
parent 800dd2014c
commit cea99397db
17 changed files with 280 additions and 160 deletions

View File

@ -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'

View File

@ -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 cant leave because you are the only admin. To make another user an admin, click their avatar, select “Change permissions…”, and select “Admin”.'; 'You cant leave because you are the only admin. To make another user an admin, click their avatar, select “Change permissions…”, and select “Admin”.';
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}>

View File

@ -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?: 'Notification' } { __typename?: 'Notified' }
& Pick<Notification, 'createdAt' | 'read' | 'id' | 'actionType'> & Pick<Notified, 'id' | 'read' | 'readAt'>
& { entity: ( & { notification: (
{ __typename?: 'NotificationEntity' } { __typename?: 'Notification' }
& Pick<NotificationEntity, 'id' | 'type' | 'name'> & Pick<Notification, 'id' | 'actionType' | 'createdAt'>
), actor: ( & { causedBy: (
{ __typename?: 'NotificationActor' } { __typename?: 'NotificationCausedBy' }
& Pick<NotificationActor, 'id' | 'type' | 'name'> & Pick<NotificationCausedBy, 'username' | 'fullname' | 'id'>
) }
) } ) }
)>, 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
read
id id
entity { read
readAt
notification {
id id
type actionType
name causedBy {
username
fullname
id
}
createdAt
} }
actor {
id
type
name
}
actionType
} }
me { me {
user { user {
@ -5527,4 +5530,4 @@ export function useUsersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<User
} }
export type UsersQueryHookResult = ReturnType<typeof useUsersQuery>; export type UsersQueryHookResult = ReturnType<typeof useUsersQuery>;
export type UsersLazyQueryHookResult = ReturnType<typeof useUsersLazyQuery>; export type UsersLazyQueryHookResult = ReturnType<typeof useUsersLazyQuery>;
export type UsersQueryResult = Apollo.QueryResult<UsersQuery, UsersQueryVariables>; export type UsersQueryResult = Apollo.QueryResult<UsersQuery, UsersQueryVariables>;

View File

@ -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
read
id id
entity { read
readAt
notification {
id id
type actionType
name causedBy {
username
fullname
id
}
createdAt
} }
actor {
id
type
name
}
actionType
} }
me { me {
user { user {

View File

@ -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 err != nil { if appConfig.Job.Enabled {
return err jobConfig := appConfig.Job.GetJobConfig()
server, err = machinery.NewServer(&jobConfig)
if err != nil {
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)
}, },

View File

@ -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(&notification.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")

View File

@ -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),
} }
} }

View File

@ -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))
} }

View File

@ -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 {

View File

@ -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) {

View File

@ -33,6 +33,6 @@ type Notified {
id: ID! id: ID!
notification: Notification! notification: Notification!
read: Boolean! read: Boolean!
read_at: Time readAt: Time
} }

View File

@ -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
View 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
}

View File

@ -1,4 +1,4 @@
package notification package jobs
import ( import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"

View File

@ -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
}

View File

@ -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

View 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
);