refactor: redesign notification table design

This commit is contained in:
Jordan Knott 2021-10-26 21:29:49 -05:00
parent d5d85c5e30
commit 54553cfbdd
8 changed files with 153 additions and 274 deletions

View File

@ -26,18 +26,18 @@ type LabelColor struct {
type Notification struct {
NotificationID uuid.UUID `json:"notification_id"`
NotificationObjectID uuid.UUID `json:"notification_object_id"`
NotifierID uuid.UUID `json:"notifier_id"`
Read bool `json:"read"`
CausedBy uuid.UUID `json:"caused_by"`
ActionType string `json:"action_type"`
Data json.RawMessage `json:"data"`
CreatedOn time.Time `json:"created_on"`
}
type NotificationObject struct {
NotificationObjectID uuid.UUID `json:"notification_object_id"`
EntityID uuid.UUID `json:"entity_id"`
ActionType int32 `json:"action_type"`
ActorID uuid.UUID `json:"actor_id"`
EntityType int32 `json:"entity_type"`
CreatedOn time.Time `json:"created_on"`
type NotificationNotified struct {
NotifiedID uuid.UUID `json:"notified_id"`
NotificationID uuid.UUID `json:"notification_id"`
UserID uuid.UUID `json:"user_id"`
Read bool `json:"read"`
ReadAt sql.NullTime `json:"read_at"`
}
type Organization struct {

View File

@ -5,86 +5,129 @@ package db
import (
"context"
"database/sql"
"encoding/json"
"time"
"github.com/google/uuid"
)
const createNotification = `-- name: CreateNotification :one
INSERT INTO notification(notification_object_id, notifier_id)
VALUES ($1, $2) RETURNING notification_id, notification_object_id, notifier_id, read
INSERT INTO notification (caused_by, data, action_type, created_on)
VALUES ($1, $2, $3, $4) RETURNING notification_id, caused_by, action_type, data, created_on
`
type CreateNotificationParams struct {
NotificationObjectID uuid.UUID `json:"notification_object_id"`
NotifierID uuid.UUID `json:"notifier_id"`
CausedBy uuid.UUID `json:"caused_by"`
Data json.RawMessage `json:"data"`
ActionType string `json:"action_type"`
CreatedOn time.Time `json:"created_on"`
}
func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotificationParams) (Notification, error) {
row := q.db.QueryRowContext(ctx, createNotification, arg.NotificationObjectID, arg.NotifierID)
row := q.db.QueryRowContext(ctx, createNotification,
arg.CausedBy,
arg.Data,
arg.ActionType,
arg.CreatedOn,
)
var i Notification
err := row.Scan(
&i.NotificationID,
&i.NotificationObjectID,
&i.NotifierID,
&i.Read,
)
return i, err
}
const createNotificationObject = `-- name: CreateNotificationObject :one
INSERT INTO notification_object(entity_type, action_type, entity_id, created_on, actor_id)
VALUES ($1, $2, $3, $4, $5) RETURNING notification_object_id, entity_id, action_type, actor_id, entity_type, created_on
`
type CreateNotificationObjectParams struct {
EntityType int32 `json:"entity_type"`
ActionType int32 `json:"action_type"`
EntityID uuid.UUID `json:"entity_id"`
CreatedOn time.Time `json:"created_on"`
ActorID uuid.UUID `json:"actor_id"`
}
func (q *Queries) CreateNotificationObject(ctx context.Context, arg CreateNotificationObjectParams) (NotificationObject, error) {
row := q.db.QueryRowContext(ctx, createNotificationObject,
arg.EntityType,
arg.ActionType,
arg.EntityID,
arg.CreatedOn,
arg.ActorID,
)
var i NotificationObject
err := row.Scan(
&i.NotificationObjectID,
&i.EntityID,
&i.CausedBy,
&i.ActionType,
&i.ActorID,
&i.EntityType,
&i.Data,
&i.CreatedOn,
)
return i, err
}
const getAllNotificationsForUserID = `-- name: GetAllNotificationsForUserID :many
SELECT n.notification_id, n.notification_object_id, n.notifier_id, n.read FROM notification as n
INNER JOIN notification_object as no ON no.notification_object_id = n.notification_object_id
WHERE n.notifier_id = $1 ORDER BY no.created_on DESC
const createNotificationNotifed = `-- name: CreateNotificationNotifed :one
INSERT INTO notification_notified (notification_id, user_id) VALUES ($1, $2) RETURNING notified_id, notification_id, user_id, read, read_at
`
func (q *Queries) GetAllNotificationsForUserID(ctx context.Context, notifierID uuid.UUID) ([]Notification, error) {
rows, err := q.db.QueryContext(ctx, getAllNotificationsForUserID, notifierID)
type CreateNotificationNotifedParams struct {
NotificationID uuid.UUID `json:"notification_id"`
UserID uuid.UUID `json:"user_id"`
}
func (q *Queries) CreateNotificationNotifed(ctx context.Context, arg CreateNotificationNotifedParams) (NotificationNotified, error) {
row := q.db.QueryRowContext(ctx, createNotificationNotifed, arg.NotificationID, arg.UserID)
var i NotificationNotified
err := row.Scan(
&i.NotifiedID,
&i.NotificationID,
&i.UserID,
&i.Read,
&i.ReadAt,
)
return i, err
}
const getAllNotificationsForUserID = `-- name: GetAllNotificationsForUserID :many
SELECT notified_id, nn.notification_id, nn.user_id, read, read_at, n.notification_id, caused_by, action_type, data, created_on, user_account.user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active FROM notification_notified AS nn
INNER JOIN notification AS n ON n.notification_id = nn.notification_id
LEFT JOIN user_account ON user_account.user_id = n.caused_by
WHERE nn.user_id = $1
`
type GetAllNotificationsForUserIDRow struct {
NotifiedID uuid.UUID `json:"notified_id"`
NotificationID uuid.UUID `json:"notification_id"`
UserID uuid.UUID `json:"user_id"`
Read bool `json:"read"`
ReadAt sql.NullTime `json:"read_at"`
NotificationID_2 uuid.UUID `json:"notification_id_2"`
CausedBy uuid.UUID `json:"caused_by"`
ActionType string `json:"action_type"`
Data json.RawMessage `json:"data"`
CreatedOn time.Time `json:"created_on"`
UserID_2 uuid.UUID `json:"user_id_2"`
CreatedAt time.Time `json:"created_at"`
Email string `json:"email"`
Username string `json:"username"`
PasswordHash string `json:"password_hash"`
ProfileBgColor string `json:"profile_bg_color"`
FullName string `json:"full_name"`
Initials string `json:"initials"`
ProfileAvatarUrl sql.NullString `json:"profile_avatar_url"`
RoleCode string `json:"role_code"`
Bio string `json:"bio"`
Active bool `json:"active"`
}
func (q *Queries) GetAllNotificationsForUserID(ctx context.Context, userID uuid.UUID) ([]GetAllNotificationsForUserIDRow, error) {
rows, err := q.db.QueryContext(ctx, getAllNotificationsForUserID, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Notification
var items []GetAllNotificationsForUserIDRow
for rows.Next() {
var i Notification
var i GetAllNotificationsForUserIDRow
if err := rows.Scan(
&i.NotifiedID,
&i.NotificationID,
&i.NotificationObjectID,
&i.NotifierID,
&i.UserID,
&i.Read,
&i.ReadAt,
&i.NotificationID_2,
&i.CausedBy,
&i.ActionType,
&i.Data,
&i.CreatedOn,
&i.UserID_2,
&i.CreatedAt,
&i.Email,
&i.Username,
&i.PasswordHash,
&i.ProfileBgColor,
&i.FullName,
&i.Initials,
&i.ProfileAvatarUrl,
&i.RoleCode,
&i.Bio,
&i.Active,
); err != nil {
return nil, err
}
@ -99,79 +142,16 @@ func (q *Queries) GetAllNotificationsForUserID(ctx context.Context, notifierID u
return items, nil
}
const getEntityForNotificationID = `-- name: GetEntityForNotificationID :one
SELECT no.created_on, no.entity_id, no.entity_type, no.action_type, no.actor_id FROM notification as n
INNER JOIN notification_object as no ON no.notification_object_id = n.notification_object_id
WHERE n.notification_id = $1
const markNotificationAsRead = `-- name: MarkNotificationAsRead :exec
UPDATE notification_notified SET read = true, read_at = $2 WHERE user_id = $1
`
type GetEntityForNotificationIDRow struct {
CreatedOn time.Time `json:"created_on"`
EntityID uuid.UUID `json:"entity_id"`
EntityType int32 `json:"entity_type"`
ActionType int32 `json:"action_type"`
ActorID uuid.UUID `json:"actor_id"`
type MarkNotificationAsReadParams struct {
UserID uuid.UUID `json:"user_id"`
ReadAt sql.NullTime `json:"read_at"`
}
func (q *Queries) GetEntityForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetEntityForNotificationIDRow, error) {
row := q.db.QueryRowContext(ctx, getEntityForNotificationID, notificationID)
var i GetEntityForNotificationIDRow
err := row.Scan(
&i.CreatedOn,
&i.EntityID,
&i.EntityType,
&i.ActionType,
&i.ActorID,
)
return i, err
}
const getEntityIDForNotificationID = `-- name: GetEntityIDForNotificationID :one
SELECT no.entity_id FROM notification as n
INNER JOIN notification_object as no ON no.notification_object_id = n.notification_object_id
WHERE n.notification_id = $1
`
func (q *Queries) GetEntityIDForNotificationID(ctx context.Context, notificationID uuid.UUID) (uuid.UUID, error) {
row := q.db.QueryRowContext(ctx, getEntityIDForNotificationID, notificationID)
var entity_id uuid.UUID
err := row.Scan(&entity_id)
return entity_id, err
}
const getNotificationForNotificationID = `-- name: GetNotificationForNotificationID :one
SELECT n.notification_id, n.notification_object_id, n.notifier_id, n.read, no.notification_object_id, no.entity_id, no.action_type, no.actor_id, no.entity_type, no.created_on FROM notification as n
INNER JOIN notification_object as no ON no.notification_object_id = n.notification_object_id
WHERE n.notification_id = $1
`
type GetNotificationForNotificationIDRow struct {
NotificationID uuid.UUID `json:"notification_id"`
NotificationObjectID uuid.UUID `json:"notification_object_id"`
NotifierID uuid.UUID `json:"notifier_id"`
Read bool `json:"read"`
NotificationObjectID_2 uuid.UUID `json:"notification_object_id_2"`
EntityID uuid.UUID `json:"entity_id"`
ActionType int32 `json:"action_type"`
ActorID uuid.UUID `json:"actor_id"`
EntityType int32 `json:"entity_type"`
CreatedOn time.Time `json:"created_on"`
}
func (q *Queries) GetNotificationForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetNotificationForNotificationIDRow, error) {
row := q.db.QueryRowContext(ctx, getNotificationForNotificationID, notificationID)
var i GetNotificationForNotificationIDRow
err := row.Scan(
&i.NotificationID,
&i.NotificationObjectID,
&i.NotifierID,
&i.Read,
&i.NotificationObjectID_2,
&i.EntityID,
&i.ActionType,
&i.ActorID,
&i.EntityType,
&i.CreatedOn,
)
return i, err
func (q *Queries) MarkNotificationAsRead(ctx context.Context, arg MarkNotificationAsReadParams) error {
_, err := q.db.ExecContext(ctx, markNotificationAsRead, arg.UserID, arg.ReadAt)
return err
}

View File

@ -16,7 +16,7 @@ type Querier interface {
CreateInvitedUser(ctx context.Context, email string) (UserAccountInvited, error)
CreateLabelColor(ctx context.Context, arg CreateLabelColorParams) (LabelColor, error)
CreateNotification(ctx context.Context, arg CreateNotificationParams) (Notification, error)
CreateNotificationObject(ctx context.Context, arg CreateNotificationObjectParams) (NotificationObject, error)
CreateNotificationNotifed(ctx context.Context, arg CreateNotificationNotifedParams) (NotificationNotified, error)
CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error)
CreatePersonalProject(ctx context.Context, arg CreatePersonalProjectParams) (Project, error)
CreatePersonalProjectLink(ctx context.Context, arg CreatePersonalProjectLinkParams) (PersonalProject, error)
@ -61,7 +61,7 @@ type Querier interface {
DeleteUserAccountInvitedForEmail(ctx context.Context, email string) error
DoesUserExist(ctx context.Context, arg DoesUserExistParams) (bool, error)
GetActivityForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskActivity, error)
GetAllNotificationsForUserID(ctx context.Context, notifierID uuid.UUID) ([]Notification, error)
GetAllNotificationsForUserID(ctx context.Context, userID uuid.UUID) ([]GetAllNotificationsForUserIDRow, error)
GetAllOrganizations(ctx context.Context) ([]Organization, error)
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
GetAllTaskGroups(ctx context.Context) ([]TaskGroup, error)
@ -78,8 +78,6 @@ type Querier interface {
GetCommentsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskComment, error)
GetConfirmTokenByEmail(ctx context.Context, email string) (UserAccountConfirmToken, error)
GetConfirmTokenByID(ctx context.Context, confirmTokenID uuid.UUID) (UserAccountConfirmToken, error)
GetEntityForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetEntityForNotificationIDRow, error)
GetEntityIDForNotificationID(ctx context.Context, notificationID uuid.UUID) (uuid.UUID, error)
GetInvitedMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]GetInvitedMembersForProjectIDRow, error)
GetInvitedUserAccounts(ctx context.Context) ([]UserAccountInvited, error)
GetInvitedUserByEmail(ctx context.Context, email string) (UserAccountInvited, error)
@ -89,7 +87,6 @@ type Querier interface {
GetMemberData(ctx context.Context, projectID uuid.UUID) ([]UserAccount, error)
GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
GetNotificationForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetNotificationForNotificationIDRow, error)
GetPersonalProjectsForUserID(ctx context.Context, userID uuid.UUID) ([]Project, error)
GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error)
GetProjectIDForTask(ctx context.Context, taskID uuid.UUID) (uuid.UUID, error)
@ -134,6 +131,7 @@ type Querier interface {
GetUserRolesForProject(ctx context.Context, arg GetUserRolesForProjectParams) (GetUserRolesForProjectRow, error)
HasActiveUser(ctx context.Context) (bool, error)
HasAnyUser(ctx context.Context) (bool, error)
MarkNotificationAsRead(ctx context.Context, arg MarkNotificationAsReadParams) error
SetFirstUserActive(ctx context.Context) (UserAccount, error)
SetInactiveLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) error
SetPublicOn(ctx context.Context, arg SetPublicOnParams) (Project, error)

View File

@ -1,27 +1,15 @@
-- name: GetAllNotificationsForUserID :many
SELECT n.* FROM notification as n
INNER JOIN notification_object as no ON no.notification_object_id = n.notification_object_id
WHERE n.notifier_id = $1 ORDER BY no.created_on DESC;
SELECT * FROM notification_notified AS nn
INNER JOIN notification AS n ON n.notification_id = nn.notification_id
LEFT JOIN user_account ON user_account.user_id = n.caused_by
WHERE nn.user_id = $1;
-- name: GetNotificationForNotificationID :one
SELECT n.*, no.* FROM notification as n
INNER JOIN notification_object as no ON no.notification_object_id = n.notification_object_id
WHERE n.notification_id = $1;
-- name: CreateNotificationObject :one
INSERT INTO notification_object(entity_type, action_type, entity_id, created_on, actor_id)
VALUES ($1, $2, $3, $4, $5) RETURNING *;
-- name: GetEntityIDForNotificationID :one
SELECT no.entity_id FROM notification as n
INNER JOIN notification_object as no ON no.notification_object_id = n.notification_object_id
WHERE n.notification_id = $1;
-- name: GetEntityForNotificationID :one
SELECT no.created_on, no.entity_id, no.entity_type, no.action_type, no.actor_id FROM notification as n
INNER JOIN notification_object as no ON no.notification_object_id = n.notification_object_id
WHERE n.notification_id = $1;
-- name: MarkNotificationAsRead :exec
UPDATE notification_notified SET read = true, read_at = $2 WHERE user_id = $1;
-- name: CreateNotification :one
INSERT INTO notification(notification_object_id, notifier_id)
VALUES ($1, $2) RETURNING *;
INSERT INTO notification (caused_by, data, action_type, created_on)
VALUES ($1, $2, $3, $4) RETURNING *;
-- name: CreateNotificationNotifed :one
INSERT INTO notification_notified (notification_id, user_id) VALUES ($1, $2) RETURNING *;

View File

@ -222,16 +222,6 @@ func ConvertToRoleCode(r string) RoleCode {
return RoleCodeObserver
}
// GetEntityType converts integer to EntityType enum
func GetEntityType(entityType int32) EntityType {
switch entityType {
case 1:
return EntityTypeTask
default:
panic("Not a valid entity type!")
}
}
// GetActionType converts integer to ActionType enum
func GetActionType(actionType int32) ActionType {
switch actionType {

View File

@ -6,14 +6,12 @@ package graph
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jordanknott/taskcafe/internal/db"
"github.com/jordanknott/taskcafe/internal/logger"
log "github.com/sirupsen/logrus"
)
func (r *notificationResolver) ID(ctx context.Context, obj *db.Notification) (uuid.UUID, error) {
@ -21,12 +19,7 @@ func (r *notificationResolver) ID(ctx context.Context, obj *db.Notification) (uu
}
func (r *notificationResolver) ActionType(ctx context.Context, obj *db.Notification) (ActionType, error) {
entity, err := r.Repository.GetEntityForNotificationID(ctx, obj.NotificationID)
if err != nil {
return ActionTypeTaskMemberAdded, err
}
actionType := GetActionType(entity.ActionType)
return actionType, nil
return ActionTypeTaskMemberAdded, nil // TODO
}
func (r *notificationResolver) CausedBy(ctx context.Context, obj *db.Notification) (*NotificationCausedBy, error) {
@ -38,26 +31,42 @@ func (r *notificationResolver) Data(ctx context.Context, obj *db.Notification) (
}
func (r *notificationResolver) CreatedAt(ctx context.Context, obj *db.Notification) (*time.Time, error) {
entity, err := r.Repository.GetEntityForNotificationID(ctx, obj.NotificationID)
if err != nil {
return &time.Time{}, err
}
return &entity.CreatedOn, nil
return &obj.CreatedOn, nil
}
func (r *queryResolver) Notifications(ctx context.Context) ([]Notified, error) {
userID, ok := GetUserID(ctx)
logger.New(ctx).Info("fetching notifications")
if !ok {
return []db.Notification{}, errors.New("user id is missing")
return []Notified{}, nil
}
notifications, err := r.Repository.GetAllNotificationsForUserID(ctx, userID)
if err == sql.ErrNoRows {
return []db.Notification{}, nil
return []Notified{}, nil
} else if err != nil {
return []db.Notification{}, err
return []Notified{}, err
}
return notifications, nil
userNotifications := []Notified{}
for _, notified := range notifications {
var readAt *time.Time
if notified.ReadAt.Valid {
readAt = &notified.ReadAt.Time
}
n := Notified{
ID: notified.NotifiedID,
Read: notified.Read,
ReadAt: readAt,
Notification: &db.Notification{
NotificationID: notified.NotificationID,
CausedBy: notified.CausedBy,
ActionType: notified.ActionType,
Data: notified.Data,
CreatedOn: notified.CreatedOn,
},
}
userNotifications = append(userNotifications, n)
}
return userNotifications, nil
}
func (r *subscriptionResolver) NotificationAdded(ctx context.Context) (<-chan *Notified, error) {
@ -68,42 +77,3 @@ func (r *subscriptionResolver) NotificationAdded(ctx context.Context) (<-chan *N
func (r *Resolver) Notification() NotificationResolver { return &notificationResolver{r} }
type notificationResolver struct{ *Resolver }
// !!! WARNING !!!
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
// one last chance to move it out of harms way if you want. There are two reasons this happens:
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
func (r *notificationResolver) Entity(ctx context.Context, obj *db.Notification) (*NotificationEntity, error) {
logger.New(ctx).WithFields(log.Fields{"notificationID": obj.NotificationID}).Info("fetching entity for notification")
entity, err := r.Repository.GetEntityForNotificationID(ctx, obj.NotificationID)
logger.New(ctx).WithFields(log.Fields{"entityID": entity.EntityID}).Info("fetched entity")
if err != nil {
return &NotificationEntity{}, err
}
entityType := GetEntityType(entity.EntityType)
switch entityType {
case EntityTypeTask:
task, err := r.Repository.GetTaskByID(ctx, entity.EntityID)
if err != nil {
return &NotificationEntity{}, err
}
return &NotificationEntity{Type: entityType, ID: entity.EntityID, Name: task.Name}, err
default:
panic(fmt.Errorf("not implemented"))
}
}
func (r *notificationResolver) Actor(ctx context.Context, obj *db.Notification) (*NotificationActor, error) {
entity, err := r.Repository.GetEntityForNotificationID(ctx, obj.NotificationID)
if err != nil {
return &NotificationActor{}, err
}
logger.New(ctx).WithFields(log.Fields{"entityID": entity.ActorID}).Info("fetching actor")
user, err := r.Repository.GetUserAccountByID(ctx, entity.ActorID)
if err != nil {
return &NotificationActor{}, err
}
return &NotificationActor{ID: entity.ActorID, Name: user.FullName, Type: ActorTypeUser}, nil
}

View File

@ -1,15 +1,10 @@
package notification
import (
"context"
"time"
"github.com/RichardKnop/machinery/v1"
"github.com/RichardKnop/machinery/v1/tasks"
"github.com/google/uuid"
"github.com/jordanknott/taskcafe/internal/db"
log "github.com/sirupsen/logrus"
)
func RegisterTasks(server *machinery.Server, repo db.Repository) {
@ -24,23 +19,6 @@ type NotificationTasks struct {
}
func (m *NotificationTasks) TaskMemberWasAdded(taskID, notifierID, notifiedID string) (bool, error) {
tid := uuid.MustParse(taskID)
notifier := uuid.MustParse(notifierID)
notified := uuid.MustParse(notifiedID)
if notifier == notified {
return true, nil
}
ctx := context.Background()
now := time.Now().UTC()
notificationObject, err := m.Repository.CreateNotificationObject(ctx, db.CreateNotificationObjectParams{EntityType: 1, EntityID: tid, ActionType: 1, ActorID: notifier, CreatedOn: now})
if err != nil {
return false, err
}
notification, err := m.Repository.CreateNotification(ctx, db.CreateNotificationParams{NotificationObjectID: notificationObject.NotificationObjectID, NotifierID: notified})
if err != nil {
return false, err
}
log.WithFields(log.Fields{"notificationID": notification.NotificationID}).Info("created new notification")
return true, nil
}
@ -49,30 +27,5 @@ type NotificationQueue struct {
}
func (n *NotificationQueue) TaskMemberWasAdded(taskID, notifier, notified uuid.UUID) error {
task := tasks.Signature{
Name: "taskMemberWasAdded",
Args: []tasks.Arg{
{
Name: "taskID",
Type: "string",
Value: taskID.String(),
},
{
Name: "notifierID",
Type: "string",
Value: notifier.String(),
},
{
Name: "notifiedID",
Type: "string",
Value: notified.String(),
},
},
}
_, err := n.Server.SendTask(&task)
if err != nil {
return err
}
return nil
}

View File

@ -3,8 +3,8 @@ DROP TABLE notification CASCADE;
CREATE TABLE notification (
notification_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
actor_id uuid,
action_type int NOT NULL,
caused_by uuid NOT NULL,
action_type text NOT NULL,
data jsonb,
created_on timestamptz NOT NULL
);