feature/convert-abstract-service-to-ts #1798

Merged
konrad merged 32 commits from dpschen/frontend:feature/convert-abstract-service-to-ts into main 2022-09-06 09:26:49 +00:00
33 changed files with 367 additions and 186 deletions
Showing only changes of commit 797de0c543 - Show all commits

View File

@ -3,22 +3,25 @@ import UserModel from './user'
import FileModel from './file'
export default class AttachmentModel extends AbstractModel {
id: number
taskId: number
createdBy: UserModel
file: FileModel
created: Date
constructor(data) {
super(data)
this.createdBy = new UserModel(this.createdBy)
this.file = new FileModel(this.file)
this.created = new Date(this.created)
/** @type {number} */
this.id
}
konrad marked this conversation as resolved Outdated

Why use declare for some properties instead of only defining the property?

Why use `declare` for some properties instead of only defining the property?

I had to use declare everywhere where a property 'isn't defined'. All of them are, but typescript doesn't understand that this happens mostly in the constructor of the abstract service.
Not sure if this here is the right way to do things. Might be one reason why the build fails.

I had to use declare everywhere where a property 'isn't defined'. All of them are, but typescript doesn't understand that this happens mostly in the constructor of the abstract service. Not sure if this here is the right way to do things. Might be one reason why the build fails.

Makes sense!

Makes sense!
defaults() {
return {
id: 0,
taskId: 0,
file: FileModel,
createdBy: UserModel,
file: FileModel,
created: null,
}
}

View File

@ -1,6 +1,10 @@
import AbstractModel from './abstractModel'
export type AVATAR_PROVIDERS = 'default' | 'initials' | 'gravatar' | 'marble' | 'upload'
export default class AvatarModel extends AbstractModel {
avatarProvider: AVATAR_PROVIDERS
defaults() {
return {
avatarProvider: '',

View File

@ -1,6 +1,15 @@
import AbstractModel from './abstractModel'
export default class BackgroundImageModel extends AbstractModel {
id: number
url: string
thumb: string
info: {
author: string
authorName: string
}
blurHash: string
defaults() {
return {
id: 0,

View File

@ -3,6 +3,18 @@ import UserModel from './user'
import TaskModel from './task'
export default class BucketModel extends AbstractModel {
id: number
title: string
listId: number
limit: number
tasks: TaskModel[]
isDoneBucket: boolean
position: number
createdBy: UserModel
created: Date
updated: Date
constructor(bucket) {
super(bucket)

View File

@ -1,14 +1,15 @@
import AbstractModel from './abstractModel'
export default class CaldavTokenModel extends AbstractModel {
id: number
created: Date
constructor(data? : Object) {
super(data)
/** @type {number} */
this.id
if (this.created) {
/** @type {Date} */
this.created = new Date(this.created)
}
}

View File

@ -1,6 +1,9 @@
import AbstractModel from './abstractModel'
export default class EmailUpdateModel extends AbstractModel {
newEmail: string
password: string
defaults() {
return {
newEmail: '',

View File

@ -1,6 +1,12 @@
import AbstractModel from './abstractModel'
export default class FileModel extends AbstractModel {
id: number
mime: string
name: string
size: number
created: Date
constructor(data) {
super(data)
this.created = new Date(this.created)

View File

@ -4,6 +4,17 @@ import {colorIsDark} from '@/helpers/color/colorIsDark'
const DEFAULT_LABEL_BACKGROUND_COLOR = 'e8e8e8'
export default class LabelModel extends AbstractModel {
id: number
title: string
hexColor: string
description: string
createdBy: UserModel
listId: number
textColor: string
created: Date
updated: Date
constructor(data) {
super(data)
// FIXME: this should be empty and be definied in the client.

View File

@ -1,6 +1,10 @@
import AbstractModel from './abstractModel'
export default class LabelTask extends AbstractModel {
id: number
taskId: number
labelId: number
defaults() {
return {
id: 0,

View File

@ -2,6 +2,16 @@ import AbstractModel from './abstractModel'
import UserModel from './user'
export default class LinkShareModel extends AbstractModel {
id: number
hash: string
right: Right
sharedBy: UserModel
sharingType: number // FIXME: use correct numbers
listId: number
name: string
password: string
created: Date
updated: Date
constructor(data) {
// The constructor of AbstractModel handles all the default parsing.

View File

@ -1,72 +1,48 @@
import AbstractModel from './abstractModel'
import TaskModel from './task'
import UserModel from './user'
import type NamespaceModel from './namespace'
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
import SubscriptionModel from '@/models/subscription'
export default class ListModel extends AbstractModel {
id: number
title: string
description: string
owner: UserModel
tasks: TaskModel[]
namespaceId: NamespaceModel['id']
isArchived: boolean
hexColor: string
identifier: string
backgroundInformation: any
isFavorite: boolean
subscription: SubscriptionModel
position: number
backgroundBlurHash: string
created: Date
updated: Date
constructor(data) {
super(data)
this.owner = new UserModel(this.owner)
/** @type {number} */
this.id
/** @type {string} */
this.title
/** @type {string} */
this.description
/** @type {UserModel} */
this.owner
/** @type {TaskModel[]} */
this.tasks
// Make all tasks to task models
this.tasks = this.tasks.map(t => {
return new TaskModel(t)
})
/** @type {number} */
this.namespaceId
/** @type {boolean} */
this.isArchived
/** @type {string} */
this.hexColor
if (this.hexColor !== '' && this.hexColor.substring(0, 1) !== '#') {
this.hexColor = '#' + this.hexColor
}
/** @type {string} */
this.identifier
/** @type */
this.backgroundInformation
/** @type {boolean} */
this.isFavorite
/** @type */
this.subscription
if (typeof this.subscription !== 'undefined' && this.subscription !== null) {
this.subscription = new SubscriptionModel(this.subscription)
}
/** @type {number} */
this.position
/** @type {Date} */
this.created = new Date(this.created)
/** @type {Date} */
this.updated = new Date(this.updated)
}

View File

@ -1,7 +1,12 @@
import AbstractModel from './abstractModel'
import ListModel from './list'
import NamespaceModel from './namespace'
export default class ListDuplicateModel extends AbstractModel {
listId: number
namespaceId: NamespaceModel['id']
list: ListModel
constructor(data) {
super(data)
this.list = new ListModel(this.list)

View File

@ -4,6 +4,18 @@ import UserModel from './user'
import SubscriptionModel from '@/models/subscription'
export default class NamespaceModel extends AbstractModel {
id: number
title: string
description: string
owner: UserModel
lists: ListModel[]
isArchived: boolean
hexColor: string
subscription: SubscriptionModel
created: Date
updated: Date
constructor(data) {
super(data)
@ -11,7 +23,6 @@ export default class NamespaceModel extends AbstractModel {
this.hexColor = '#' + this.hexColor
}
/** @type {ListModel[]} */
this.lists = this.lists.map(l => {
return new ListModel(l)
})
@ -22,15 +33,6 @@ export default class NamespaceModel extends AbstractModel {
this.subscription = new SubscriptionModel(this.subscription)
}
/** @type {number} */
this.id
/** @type {string} */
this.title
/** @type {boolean} */
this.isArchived
this.created = new Date(this.created)
this.updated = new Date(this.updated)
}

View File

@ -5,35 +5,86 @@ import TaskModel from '@/models/task'
import TaskCommentModel from '@/models/taskComment'
import ListModel from '@/models/list'
import TeamModel from '@/models/team'
import names from './constants/notificationNames.json'
export const NOTIFICATION_NAMES = {
'TASK_COMMENT': 'task.comment',
'TASK_ASSIGNED': 'task.assigned',
'TASK_DELETED': 'task.deleted',
'LIST_CREATED': 'list.created',
'TEAM_MEMBER_ADDED': 'team.member.added',
} as const
interface Notification {
doer: UserModel
}
interface NotificationTask extends Notification {
task: TaskModel
comment: TaskCommentModel
}
interface NotificationAssigned extends Notification {
task: TaskModel
assignee: UserModel
}
interface NotificationDeleted extends Notification {
task: TaskModel
}
interface NotificationCreated extends Notification {
task: TaskModel
}
interface NotificationMemberAdded extends Notification {
member: UserModel
team: TeamModel
}
export default class NotificationModel extends AbstractModel {
id: number
name: string
notification: NotificationTask | NotificationAssigned | NotificationDeleted | NotificationCreated | NotificationMemberAdded
read: boolean
readAt: Date | null
created: Date
constructor(data) {
super(data)
switch (this.name) {
case names.TASK_COMMENT:
this.notification.doer = new UserModel(this.notification.doer)
this.notification.task = new TaskModel(this.notification.task)
this.notification.comment = new TaskCommentModel(this.notification.comment)
case NOTIFICATION_NAMES.TASK_COMMENT:
this.notification = {
doer: new UserModel(this.notification.doer),
task: new TaskModel(this.notification.task),
comment: new TaskCommentModel(this.notification.comment),
}
break
case names.TASK_ASSIGNED:
this.notification.doer = new UserModel(this.notification.doer)
this.notification.task = new TaskModel(this.notification.task)
this.notification.assignee = new UserModel(this.notification.assignee)
case NOTIFICATION_NAMES.TASK_ASSIGNED:
this.notification = {
doer: new UserModel(this.notification.doer),
task: new TaskModel(this.notification.task),
assignee: new UserModel(this.notification.assignee),
}
break
case names.TASK_DELETED:
this.notification.doer = new UserModel(this.notification.doer)
this.notification.task = new TaskModel(this.notification.task)
case NOTIFICATION_NAMES.TASK_DELETED:
this.notification = {
doer: new UserModel(this.notification.doer),
task: new TaskModel(this.notification.task),
}
break
case names.LIST_CREATED:
this.notification.doer = new UserModel(this.notification.doer)
this.notification.list = new ListModel(this.notification.list)
case NOTIFICATION_NAMES.LIST_CREATED:
this.notification = {
doer: new UserModel(this.notification.doer),
list: new ListModel(this.notification.list),
}
break
case names.TEAM_MEMBER_ADDED:
this.notification.doer = new UserModel(this.notification.doer)
this.notification.member = new UserModel(this.notification.member)
this.notification.team = new TeamModel(this.notification.team)
case NOTIFICATION_NAMES.TEAM_MEMBER_ADDED:
this.notification = {
doer: new UserModel(this.notification.doer),
member: new UserModel(this.notification.member),
team: new TeamModel(this.notification.team),
}
break
}
@ -55,9 +106,9 @@ export default class NotificationModel extends AbstractModel {
let who = ''
switch (this.name) {
case names.TASK_COMMENT:
case NOTIFICATION_NAMES.TASK_COMMENT:
return `commented on ${this.notification.task.getTextIdentifier()}`
case names.TASK_ASSIGNED:
case NOTIFICATION_NAMES.TASK_ASSIGNED:
who = `${this.notification.assignee.getDisplayName()}`
if (user !== null && user.id === this.notification.assignee.id) {
@ -65,11 +116,11 @@ export default class NotificationModel extends AbstractModel {
}
return `assigned ${who} to ${this.notification.task.getTextIdentifier()}`
case names.TASK_DELETED:
case NOTIFICATION_NAMES.TASK_DELETED:
return `deleted ${this.notification.task.getTextIdentifier()}`
case names.LIST_CREATED:
case NOTIFICATION_NAMES.LIST_CREATED:
return `created ${this.notification.list.title}`
case names.TEAM_MEMBER_ADDED:
case NOTIFICATION_NAMES.TEAM_MEMBER_ADDED:
who = `${this.notification.member.getDisplayName()}`
if (user !== null && user.id === this.notification.member.id) {

View File

@ -1,6 +1,10 @@
import AbstractModel from './abstractModel'
export default class PasswordResetModel extends AbstractModel {
token: string
newPassword: string
email: string
constructor(data) {
super(data)

View File

@ -1,6 +1,9 @@
import AbstractModel from './abstractModel'
export default class PasswordUpdateModel extends AbstractModel {
newPassword: string
oldPassword: string
defaults() {
return {
newPassword: '',

View File

@ -2,6 +2,23 @@ import AbstractModel from '@/models/abstractModel'
import UserModel from '@/models/user'
export default class SavedFilterModel extends AbstractModel {
id: 0
title: string
description: string
filters: {
sortBy: ('done' | 'id')[]
orderBy: ('asc' | 'desc')[]
filterBy: 'done'[]
filterValue: 'false'[]
filterComparator: 'equals'[]
filterConcat: 'and'
filterIncludeNulls: boolean
}
owner: any
created: Date
updated: Date
constructor(data) {
super(data)

View File

@ -2,22 +2,17 @@ import AbstractModel from '@/models/abstractModel'
import UserModel from '@/models/user'
export default class SubscriptionModel extends AbstractModel {
id: number
entity: string // FIXME: correct type?
entityId: number // FIXME: correct type?
user: UserModel
created: Date
constructor(data) {
super(data)
/** @type {number} */
this.id
/** @type {string} */
this.entity
/** @type {number} */
this.entityId
/** @type {Date} */
this.created = new Date(this.created)
/** @type {UserModel} */
this.user = new UserModel(this.user)
}

View File

@ -2,69 +2,86 @@ import AbstractModel from './abstractModel'
import UserModel from './user'
import LabelModel from './label'
import AttachmentModel from './attachment'
import {REPEAT_MODE_DEFAULT} from './constants/taskRepeatModes'
import SubscriptionModel from '@/models/subscription'
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
import type ListModel from './list'
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
export const TASK_DEFAULT_COLOR = '#1973ff'
export const TASK_REPEAT_MODES = {
'REPEAT_MODE_DEFAULT': 0,
'REPEAT_MODE_MONTH': 1,
'REPEAT_MODE_FROM_CURRENT_DATE': 2,
} as const
export type TaskRepeatMode = typeof TASK_REPEAT_MODES[keyof typeof TASK_REPEAT_MODES]
export interface RepeatAfter {
type: 'hours' | 'weeks' | 'months' | 'years' | 'days'
amount: number
}
export default class TaskModel extends AbstractModel {
constructor(data) {
id: number
title: string
description: string
done: boolean
doneAt: Date | null
priority: 0
labels: LabelModel[]
assignees: UserModel[]
dueDate: Date | null
startDate: Date | null
endDate: Date | null
repeatAfter: number | RepeatAfter
repeatFromCurrentDate: boolean
repeatMode: TaskRepeatMode
reminderDates: Date[]
parentTaskId: TaskModel['id']
hexColor: string
percentDone: number
relatedTasks: { [relationKind: string]: TaskModel } // FIXME: use relationKinds
attachments: AttachmentModel[]
identifier: string
index: number
isFavorite: boolean
subscription: SubscriptionModel
position: number
kanbanPosition: number
createdBy: UserModel
created: Date
updated: Date
dpschen marked this conversation as resolved Outdated

Probably my lack of typescript knowledge speaking here: I've seen this a few times now, is there a special reason to use IList['id'] instead of IList.id?

Probably my lack of typescript knowledge speaking here: I've seen this a few times now, is there a special reason to use `IList['id']` instead of `IList.id`?

IList is a type and not a namespace. Afaik you can only access types in namespaces like IList.id. To access properties of types you need that bracket notation.

IList is a type and not a namespace. Afaik you can only access types in namespaces like `IList.id`. To access properties of types you need that bracket notation.
listId: ListModel['id'] // Meta, only used when creating a new task
constructor(data: Partial<TaskModel>) {
super(data)
/** @type {number} */
this.id = Number(this.id)
/** @type {string} */
this.title = this.title?.trim()
/** @type {string} */
this.description
/** @type {boolean} */
this.done
/** @type */
this.doneAt = parseDateOrNull(this.doneAt)
/** @type {number} */
this.priority
/** @type {LabelModel[]} */
this.labels = this.labels
.map(l => new LabelModel(l))
.sort((f, s) => f.title > s.title ? 1 : -1)
/** @type {UserModel[]} */
// Parse the assignees into user models
this.assignees = this.assignees.map(a => {
return new UserModel(a)
})
/** @type {Date} */
this.dueDate = parseDateOrNull(this.dueDate)
/** @type {Date} */
this.startDate = parseDateOrNull(this.startDate)
/** @type {Date} */
this.endDate = parseDateOrNull(this.endDate)
/** @type */
this.repeatAfter
// Parse the repeat after into something usable
this.parseRepeatAfter()
/** @type {boolean} */
this.repeatFromCurrentDate
/** @type {TaskRepeatMode: 0 | 1 | 2} */
this.repeatMode
/** @type {Date[]} */
this.reminderDates = this.reminderDates.map(d => new Date(d))
// Cancel all scheduled notifications for this task to be sure to only have available notifications
@ -73,22 +90,10 @@ export default class TaskModel extends AbstractModel {
this.reminderDates.forEach(d => this.scheduleNotification(d))
})
/** @type {number} */
this.parentTaskId
/** @type {string} */
this.hexColor
if (this.hexColor !== '' && this.hexColor.substring(0, 1) !== '#') {
this.hexColor = '#' + this.hexColor
}
/** @type {number} */
this.percentDone
/** @type {{ [relationKind: string]: TaskModel }} */
this.relatedTasks
// Make all subtasks to task models
Object.keys(this.relatedTasks).forEach(relationKind => {
this.relatedTasks[relationKind] = this.relatedTasks[relationKind].map(t => {
@ -97,46 +102,21 @@ export default class TaskModel extends AbstractModel {
})
// Make all attachments to attachment models
/** @type {AttachmentModel[]} */
this.attachments = this.attachments.map(a => new AttachmentModel(a))
/** @type {string} */
this.identifier
// Set the task identifier to empty if the list does not have one
if (this.identifier === `-${this.index}`) {
this.identifier = ''
}
/** @type {number} */
this.index
/** @type {boolean} */
this.isFavorite
/** @type {SubscriptionModel} */
this.subscription
if (typeof this.subscription !== 'undefined' && this.subscription !== null) {
this.subscription = new SubscriptionModel(this.subscription)
}
/** @type {number} */
this.position
/** @type {number} */
this.kanbanPosition
/** @type {UserModel} */
this.createdBy = new UserModel(this.createdBy)
/** @type {Date} */
this.created = new Date(this.created)
/** @type {Date} */
this.updated = new Date(this.updated)
/** @type {number} */
this.listId = Number(this.listId)
}
@ -156,7 +136,7 @@ export default class TaskModel extends AbstractModel {
endDate: 0,
repeatAfter: 0,
repeatFromCurrentDate: false,
repeatMode: REPEAT_MODE_DEFAULT,
repeatMode: TASK_REPEAT_MODES.REPEAT_MODE_DEFAULT,
reminderDates: [],
parentTaskId: 0,
hexColor: '',
@ -204,7 +184,7 @@ export default class TaskModel extends AbstractModel {
* This function should only be called from the constructor.
*/
parseRepeatAfter() {
const repeatAfterHours = (this.repeatAfter / 60) / 60
const repeatAfterHours = (this.repeatAfter as number / 60) / 60
this.repeatAfter = {type: 'hours', amount: repeatAfterHours}
// if its dividable by 24, its something with days, otherwise hours

View File

@ -1,6 +1,12 @@
import AbstractModel from './abstractModel'
import type UserModel from './user'
import type TaskModel from './task'
export default class TaskAssigneeModel extends AbstractModel {
created: Date
userId: UserModel['id']
taskId: TaskModel['id']
constructor(data) {
super(data)
this.created = new Date(this.created)

View File

@ -1,7 +1,16 @@
import AbstractModel from './abstractModel'
import UserModel from './user'
import type TaskModel from './task'
export default class TaskCommentModel extends AbstractModel {
id: number
taskId: TaskModel['id']
comment: string
author: UserModel
created: Date
updated: Date
constructor(data) {
super(data)
this.author = new UserModel(this.author)
@ -16,7 +25,7 @@ export default class TaskCommentModel extends AbstractModel {
comment: '',
author: UserModel,
created: null,
update: null,
updated: null,
}
}
}

View File

@ -1,7 +1,31 @@
import AbstractModel from './abstractModel'
import UserModel from './user'
import type TaskModel from './task'
export const RELATION_KINDS = [
'subtask',
'parenttask',
'related',
'duplicates',
'blocking',
'blocked',
'precedes',
'follows',
'copiedfrom',
'copiedto',
] as const
export type RelationKind = typeof RELATION_KINDS[number]
export default class TaskRelationModel extends AbstractModel {
id: number
otherTaskId: TaskModel['id']
taskId: TaskModel['id']
relationKind: RelationKind
createdBy: UserModel
created: Date
constructor(data) {
super(data)
this.createdBy = new UserModel(this.createdBy)

View File

@ -3,6 +3,16 @@ import UserModel from './user'
import TeamMemberModel from './teamMember'
export default class TeamModel extends AbstractModel {
id: 0
name: string
description: string
members: TeamMemberModel[]
right: Right
createdBy: UserModel
created: Date
updated: Date
constructor(data) {
super(data)

View File

@ -1,6 +1,9 @@
import TeamShareBaseModel from './teamShareBase'
import type ListModel from './list'
export default class TeamListModel extends TeamShareBaseModel {
listId: ListModel['id']
defaults() {
return {
...super.defaults(),

View File

@ -1,6 +1,10 @@
import UserModel from './user'
import type ListModel from './list'
export default class TeamMemberModel extends UserModel {
admin: boolean
teamId: ListModel['id']
defaults() {
return {
...super.defaults(),

View File

@ -1,6 +1,9 @@
import TeamShareBaseModel from './teamShareBase'
import type NamespaceModel from './namespace'
export default class TeamNamespaceModel extends TeamShareBaseModel {
namespaceId: NamespaceModel['id']
defaults() {
return {
...super.defaults(),

View File

@ -1,10 +1,18 @@
import AbstractModel from './abstractModel'
import type TeamModel from './team'
import type {Right} from '@/models/constants/rights'
/**
* This class is a base class for common team sharing model.
* It is extended in a way so it can be used for namespaces as well for lists.
*/
export default class TeamShareBaseModel extends AbstractModel {
teamId: TeamModel['id']
right: Right
created: Date
updated: Date
constructor(data) {
super(data)
this.created = new Date(this.created)

View File

@ -1,6 +1,10 @@
import AbstractModel from './abstractModel'
export default class TotpModel extends AbstractModel {
secret: string
enabled: boolean
url: string
defaults() {
return {
secret: '',

View File

@ -2,30 +2,21 @@ import AbstractModel from './abstractModel'
import UserSettingsModel from '@/models/userSettings'
export default class UserModel extends AbstractModel {
id: number
email: string
username: string
name: string
created: Date
updated: Date
settings: UserSettingsModel
constructor(data) {
super(data)
/** @type {number} */
this.id
/** @type {string} */
this.email
/** @type {string} */
this.username
/** @type {string} */
this.name
/** @type {Date} */
this.created = new Date(this.created)
/** @type {Date} */
this.updated = new Date(this.updated)
/** @type {UserSettingsModel} */
this.settings
if (this.settings !== null) {
this.settings = new UserSettingsModel(this.settings)
}

View File

@ -1,7 +1,9 @@
import UserShareBaseModel from './userShareBase'
import type ListModel from './list'
// This class extends the user share model with a 'rights' parameter which is used in sharing
export default class UserListModel extends UserShareBaseModel {
listId: ListModel['id']
defaults() {
return {
...super.defaults(),

View File

@ -1,7 +1,10 @@
import UserShareBaseModel from './userShareBase'
import type NamespaceModel from './namespace'
// This class extends the user share model with a 'rights' parameter which is used in sharing
export default class UserNamespaceModel extends UserShareBaseModel {
namespaceId: NamespaceModel['id']
defaults() {
return {
...super.defaults(),

View File

@ -1,7 +1,17 @@
import AbstractModel from './abstractModel'
import type ListModel from './list'
export default class UserSettingsModel extends AbstractModel {
name: string
emailRemindersEnabled: boolean
discoverableByName: boolean
discoverableByEmail: boolean
overdueTasksRemindersEnabled: boolean
defaultListId: undefined | ListModel['id']
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
timezone: string
defaults() {
return {
name: '',

View File

@ -1,6 +1,14 @@
import AbstractModel from './abstractModel'
import type UserModel from './user'
import type {Right} from '@/models/constants/rights'
export default class UserShareBaseModel extends AbstractModel {
userId: UserModel['id']
right: Right
created: Date
updated: Date
constructor(data) {
super(data)
this.created = new Date(this.created)