chore: better service method types based on paths object #3267

Closed
WofWca wants to merge 1 commits from WofWca/frontend:type-safer-service-methods into main
35 changed files with 336 additions and 215 deletions

View File

@ -6,7 +6,7 @@ import AbstractModel from '@/models/abstractModel'
import type {IAbstract} from '@/modelTypes/IAbstract' import type {IAbstract} from '@/modelTypes/IAbstract'
import type {Right} from '@/constants/rights' import type {Right} from '@/constants/rights'
interface Paths { interface PathsAll {
konrad marked this conversation as resolved
Review

Why not call it AllPaths?

Why not call it `AllPaths`?
Review

Idk, "the most important part at the start", like naming Vue components.

Idk, "the most important part at the start", like naming Vue components.
create : string create : string
get : string get : string
getAll : string getAll : string
@ -14,6 +14,7 @@ interface Paths {
delete : string delete : string
reset?: string reset?: string
} }
type PathsGeneric = Partial<PathsAll>;
Review

Since PathsAll is only used here couldn't we make all entries optional instead?

Since `PathsAll` is only used here couldn't we make all entries optional instead?
function convertObject(o: Record<string, unknown>) { function convertObject(o: Record<string, unknown>) {
if (o instanceof Date) { if (o instanceof Date) {
@ -40,7 +41,39 @@ function prepareParams(params: Record<string, unknown | unknown[]>) {
return objectToSnakeCase(params) return objectToSnakeCase(params)
} }
export default abstract class AbstractService<Model extends IAbstract = IAbstract> { type VarsFromPathTemplate<P extends string> = P extends `${string}{${infer FirstKey}}${infer Rest}`
? [FirstKey, ...VarsFromPathTemplate<Rest>]
: [];
/**
* @example
* type T = ReplacerDictFromPath<'/teams/{teamId}/members/{username}'>;
* const dict: T = {
dpschen marked this conversation as resolved
Review

Please don't use single character var names.

Please don't use single character var names.
Review

Come on, it's just an example. What would you call it?

Come on, it's just an example. What would you call it?
Review

I know it seems picky, but when I read it I had to think for a bit until I understood. So Especially because it's an example it should be clear. How about params?

I know it seems picky, but when I read it I had to think for a bit until I understood. So Especially because it's an example it should be clear. How about params?
Review

Hope "dict" is good, to align with the type name.

Hope "dict" is good, to align with the type name.
Review

Perfect :)

Perfect :)
* teamId: 1,
* username: 'demo',
* }
*/
type ReplacerDictFromPath<P extends string> = Record<
VarsFromPathTemplate<P>[number],
string | number
>
type ReplacerDictOrNeverFromPath<P extends string | undefined | null> =
P extends string
? ReplacerDictFromPath<P>
: never;
/**
* Prohibit the use of `string` as values, i.e. require a more concrete type:
* string literal or template literal.
*/
type EnsureConst<T extends Record<string, string>> = {
[P in keyof T]: string extends T[P] ? never : T[P];
};
export default abstract class AbstractService<
Model extends IAbstract = IAbstract,
// Not sure if the default value makes sense here. Put it only because a non-optional
// param can't go after an optional one.
P extends PathsGeneric = EnsureConst<PathsGeneric>,
> {
///////////////////////////// /////////////////////////////
// Initial variable definitions // Initial variable definitions
@ -49,13 +82,7 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
http http
loading = false loading = false
uploadProgress = 0 uploadProgress = 0
paths: Paths = { paths: P
create: '',
get: '',
getAll: '',
update: '',
delete: '',
}
// This contains the total number of pages and the number of results for the current page // This contains the total number of pages and the number of results for the current page
totalPages = 0 totalPages = 0
resultCount = 0 resultCount = 0
@ -68,7 +95,7 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
* The abstract constructor. * The abstract constructor.
* @param [paths] An object with all paths. * @param [paths] An object with all paths.
*/ */
constructor(paths : Partial<Paths> = {}) { constructor(paths : EnsureConst<P> = ({} as const)) {
this.http = AuthenticatedHTTPFactory() this.http = AuthenticatedHTTPFactory()
// Set the interceptors to process every request // Set the interceptors to process every request
@ -96,7 +123,7 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
return config return config
}) })
Object.assign(this.paths, paths) this.paths = { ...paths } as P
} }
/** /**
@ -267,8 +294,13 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
* @param model The model to use. The request path is built using the values from the model. * @param model The model to use. The request path is built using the values from the model.
* @param params Optional query parameters * @param params Optional query parameters
*/ */
get(model : Model, params = {}) { // TODO refactor: if `paths.get` is undefined, it's better if `get()` method
if (this.paths.get === '') { // is undefined as well, instead of just returning `never`.
get<T extends P['get']>(
model : ReplacerDictOrNeverFromPath<T>,
params = {},
): Promise<T extends string ? Model : never> {
if (!this.paths.get) {
throw new Error('This model is not able to get data.') throw new Error('This model is not able to get data.')
} }
@ -279,7 +311,11 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
* This is a more abstract implementation which only does a get request. * This is a more abstract implementation which only does a get request.
* Services which need more flexibility can use this. * Services which need more flexibility can use this.
*/ */
async getM(url : string, model : Model = new AbstractModel({}), params: Record<string, unknown> = {}) { async getM<T extends string>(
url : T,
model : ReplacerDictFromPath<T>,
params: Record<string, unknown> = {},
) {
const cancel = this.setLoading() const cancel = this.setLoading()
model = this.beforeGet(model) model = this.beforeGet(model)
@ -312,8 +348,14 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
* @param params Optional query parameters * @param params Optional query parameters
* @param page The page to get * @param page The page to get
*/ */
async getAll(model : Model = new AbstractModel({}), params = {}, page = 1): Promise<Model[]> { // TODO refactor: ensure that if `ReplacerDict` is an empty object type then you
if (this.paths.getAll === '') { // can't pass an arbitrary object literal as `model`.
async getAll<T extends P['getAll']>(
model : ReplacerDictOrNeverFromPath<T> = new AbstractModel({}),
params = {},
page = 1,
): Promise<T extends string ? Model[] : never> {
if (!this.paths.getAll) {
throw new Error('This model is not able to get data.') throw new Error('This model is not able to get data.')
} }
@ -342,8 +384,10 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
* Performs a put request to the url specified before * Performs a put request to the url specified before
* @returns {Promise<any | never>} * @returns {Promise<any | never>}
*/ */
async create(model : Model) { async create<T extends P['create']>(
if (this.paths.create === '') { model : Model & ReplacerDictOrNeverFromPath<T>,
): Promise<T extends string ? Model : never> {
if (!this.paths.create) {
throw new Error('This model is not able to create data.') throw new Error('This model is not able to create data.')
} }
@ -384,8 +428,10 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
/** /**
* Performs a post request to the update url * Performs a post request to the update url
*/ */
update(model : Model) { update<T extends P['update']>(
if (this.paths.update === '') { model : Model & ReplacerDictOrNeverFromPath<T>,
): Promise<T extends string ? Model : never> {
if (!this.paths.update) {
throw new Error('This model is not able to update data.') throw new Error('This model is not able to update data.')
} }
@ -396,8 +442,10 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
/** /**
* Performs a delete request to the update url * Performs a delete request to the update url
*/ */
async delete(model : Model) { async delete<T extends P['delete']>(
if (this.paths.delete === '') { model : ReplacerDictOrNeverFromPath<T>,
) {
if (!this.paths.delete) {
throw new Error('This model is not able to delete data.') throw new Error('This model is not able to delete data.')
} }

View File

@ -5,13 +5,15 @@ import type { IAttachment } from '@/modelTypes/IAttachment'
import {downloadBlob} from '@/helpers/downloadBlob' import {downloadBlob} from '@/helpers/downloadBlob'
export default class AttachmentService extends AbstractService<IAttachment> { const paths = {
create: '/tasks/{taskId}/attachments',
getAll: '/tasks/{taskId}/attachments',
delete: '/tasks/{taskId}/attachments/{id}',
} as const
export default class AttachmentService extends AbstractService<IAttachment, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/tasks/{taskId}/attachments',
getAll: '/tasks/{taskId}/attachments',
delete: '/tasks/{taskId}/attachments/{id}',
})
} }
processModel(model: IAttachment) { processModel(model: IAttachment) {

View File

@ -2,13 +2,15 @@ import AbstractService from './abstractService'
import AvatarModel from '@/models/avatar' import AvatarModel from '@/models/avatar'
import type { IAvatar } from '@/modelTypes/IAvatar' import type { IAvatar } from '@/modelTypes/IAvatar'
export default class AvatarService extends AbstractService<IAvatar> { const paths = {
get: '/user/settings/avatar',
update: '/user/settings/avatar',
create: '/user/settings/avatar/upload',
} as const
Review

Why the const .. as const? Isn't that duplicated?

Why the `const .. as const`? Isn't that duplicated?
Review

Nope, otherwise it says that the type of get, getAll, create are strings, not string literals.

Nope, otherwise it says that the type of `get`, `getAll`, `create` are `string`s, not string literals.
export default class AvatarService extends AbstractService<IAvatar, typeof paths> {
constructor() { constructor() {
super({ super(paths)
get: '/user/settings/avatar',
update: '/user/settings/avatar',
create: '/user/settings/avatar/upload',
})
} }
modelFactory(data: Partial<IAvatar>) { modelFactory(data: Partial<IAvatar>) {

View File

@ -3,12 +3,14 @@ import BackgroundImageModel from '../models/backgroundImage'
import ProjectModel from '@/models/project' import ProjectModel from '@/models/project'
import type { IBackgroundImage } from '@/modelTypes/IBackgroundImage' import type { IBackgroundImage } from '@/modelTypes/IBackgroundImage'
export default class BackgroundUnsplashService extends AbstractService<IBackgroundImage> { const paths = {
getAll: '/backgrounds/unsplash/search',
update: '/projects/{projectId}/backgrounds/unsplash',
} as const
export default class BackgroundUnsplashService extends AbstractService<IBackgroundImage, typeof paths> {
constructor() { constructor() {
super({ super(paths)
getAll: '/backgrounds/unsplash/search',
update: '/projects/{projectId}/backgrounds/unsplash',
})
} }
modelFactory(data: Partial<IBackgroundImage>) { modelFactory(data: Partial<IBackgroundImage>) {

View File

@ -4,11 +4,13 @@ import ProjectModel from '@/models/project'
import type { IProject } from '@/modelTypes/IProject' import type { IProject } from '@/modelTypes/IProject'
import type { IFile } from '@/modelTypes/IFile' import type { IFile } from '@/modelTypes/IFile'
export default class BackgroundUploadService extends AbstractService { const paths = {
create: '/projects/{projectId}/backgrounds/upload',
} as const
export default class BackgroundUploadService extends AbstractService<IProject, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/projects/{projectId}/backgrounds/upload',
})
} }
useCreateInterceptor() { useCreateInterceptor() {

View File

@ -3,14 +3,16 @@ import BucketModel from '../models/bucket'
import TaskService from '@/services/task' import TaskService from '@/services/task'
import type { IBucket } from '@/modelTypes/IBucket' import type { IBucket } from '@/modelTypes/IBucket'
export default class BucketService extends AbstractService<IBucket> { const paths = {
getAll: '/projects/{projectId}/buckets',
create: '/projects/{projectId}/buckets',
update: '/projects/{projectId}/buckets/{id}',
delete: '/projects/{projectId}/buckets/{id}',
} as const
export default class BucketService extends AbstractService<IBucket, typeof paths> {
constructor() { constructor() {
super({ super(paths)
getAll: '/projects/{projectId}/buckets',
create: '/projects/{projectId}/buckets',
update: '/projects/{projectId}/buckets/{id}',
delete: '/projects/{projectId}/buckets/{id}',
})
} }
modelFactory(data: Partial<IBucket>) { modelFactory(data: Partial<IBucket>) {

View File

@ -2,13 +2,15 @@ import CaldavTokenModel from '@/models/caldavToken'
import type {ICaldavToken} from '@/modelTypes/ICaldavToken' import type {ICaldavToken} from '@/modelTypes/ICaldavToken'
import AbstractService from './abstractService' import AbstractService from './abstractService'
export default class CaldavTokenService extends AbstractService<ICaldavToken> { const paths = {
getAll: '/user/settings/token/caldav',
create: '/user/settings/token/caldav',
delete: '/user/settings/token/caldav/{id}',
} as const
export default class CaldavTokenService extends AbstractService<ICaldavToken, typeof paths> {
constructor() { constructor() {
super({ super(paths)
getAll: '/user/settings/token/caldav',
create: '/user/settings/token/caldav',
delete: '/user/settings/token/caldav/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -1,9 +1,12 @@
import type { IAbstract } from '@/modelTypes/IAbstract'
import AbstractService from './abstractService' import AbstractService from './abstractService'
export default class EmailUpdateService extends AbstractService { const paths = {
update: '/user/settings/email',
} as const
export default class EmailUpdateService extends AbstractService<IAbstract, typeof paths> {
constructor() { constructor() {
super({ super(paths)
update: '/user/settings/email',
})
} }
} }

View File

@ -3,15 +3,17 @@ import LabelModel from '@/models/label'
import type {ILabel} from '@/modelTypes/ILabel' import type {ILabel} from '@/modelTypes/ILabel'
import {colorFromHex} from '@/helpers/color/colorFromHex' import {colorFromHex} from '@/helpers/color/colorFromHex'
export default class LabelService extends AbstractService<ILabel> { const paths = {
create: '/labels',
getAll: '/labels',
get: '/labels/{id}',
update: '/labels/{id}',
delete: '/labels/{id}',
} as const
export default class LabelService extends AbstractService<ILabel, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/labels',
getAll: '/labels',
get: '/labels/{id}',
update: '/labels/{id}',
delete: '/labels/{id}',
})
} }
processModel(label) { processModel(label) {

View File

@ -2,13 +2,15 @@ import AbstractService from './abstractService'
import LabelTask from '@/models/labelTask' import LabelTask from '@/models/labelTask'
import type {ILabelTask} from '@/modelTypes/ILabelTask' import type {ILabelTask} from '@/modelTypes/ILabelTask'
export default class LabelTaskService extends AbstractService<ILabelTask> { const paths = {
create: '/tasks/{taskId}/labels',
getAll: '/tasks/{taskId}/labels',
delete: '/tasks/{taskId}/labels/{labelId}',
} as const
export default class LabelTaskService extends AbstractService<ILabelTask, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/tasks/{taskId}/labels',
getAll: '/tasks/{taskId}/labels',
delete: '/tasks/{taskId}/labels/{labelId}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,14 +2,16 @@ import AbstractService from './abstractService'
import LinkShareModel from '@/models/linkShare' import LinkShareModel from '@/models/linkShare'
import type {ILinkShare} from '@/modelTypes/ILinkShare' import type {ILinkShare} from '@/modelTypes/ILinkShare'
export default class LinkShareService extends AbstractService<ILinkShare> { const paths = {
getAll: '/projects/{projectId}/shares',
get: '/projects/{projectId}/shares/{id}',
create: '/projects/{projectId}/shares',
delete: '/projects/{projectId}/shares/{id}',
} as const
export default class LinkShareService extends AbstractService<ILinkShare, typeof paths> {
constructor() { constructor() {
super({ super(paths)
getAll: '/projects/{projectId}/shares',
get: '/projects/{projectId}/shares/{id}',
create: '/projects/{projectId}/shares',
delete: '/projects/{projectId}/shares/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,14 +2,18 @@ import AbstractService from '../abstractService'
export type MigrationConfig = { code: string } export type MigrationConfig = { code: string }
type Paths = {
update: `/migration/${string}/migrate`
}
// This service builds on top of the abstract service and basically just hides away method names. // This service builds on top of the abstract service and basically just hides away method names.
// It enables migration services to be created with minimal overhead and even better method names. // It enables migration services to be created with minimal overhead and even better method names.
export default class AbstractMigrationService extends AbstractService<MigrationConfig> { export default class AbstractMigrationService extends AbstractService<MigrationConfig, Paths> {
serviceUrlKey = '' serviceUrlKey = ''
constructor(serviceUrlKey: string) { constructor(serviceUrlKey: string) {
super({ super({
update: '/migration/' + serviceUrlKey + '/migrate', update: `/migration/${serviceUrlKey}/migrate`,
}) })
this.serviceUrlKey = serviceUrlKey this.serviceUrlKey = serviceUrlKey
} }

View File

@ -3,15 +3,17 @@ import NamespaceModel from '../models/namespace'
import type {INamespace} from '@/modelTypes/INamespace' import type {INamespace} from '@/modelTypes/INamespace'
import {colorFromHex} from '@/helpers/color/colorFromHex' import {colorFromHex} from '@/helpers/color/colorFromHex'
export default class NamespaceService extends AbstractService<INamespace> { const paths = {
create: '/namespaces',
get: '/namespaces/{id}',
getAll: '/namespaces',
update: '/namespaces/{id}',
delete: '/namespaces/{id}',
} as const
export default class NamespaceService extends AbstractService<INamespace, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/namespaces',
get: '/namespaces/{id}',
getAll: '/namespaces',
update: '/namespaces/{id}',
delete: '/namespaces/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,12 +2,14 @@ import AbstractService from '@/services/abstractService'
import NotificationModel from '@/models/notification' import NotificationModel from '@/models/notification'
import type {INotification} from '@/modelTypes/INotification' import type {INotification} from '@/modelTypes/INotification'
export default class NotificationService extends AbstractService<INotification> { const paths = {
getAll: '/notifications',
update: '/notifications/{id}',
} as const
export default class NotificationService extends AbstractService<INotification, typeof paths> {
constructor() { constructor() {
super({ super(paths)
getAll: '/notifications',
update: '/notifications/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,10 +2,11 @@ import AbstractService from './abstractService'
import PasswordResetModel from '@/models/passwordReset' import PasswordResetModel from '@/models/passwordReset'
import type {IPasswordReset} from '@/modelTypes/IPasswordReset' import type {IPasswordReset} from '@/modelTypes/IPasswordReset'
export default class PasswordResetService extends AbstractService<IPasswordReset> { const paths = {} as const
export default class PasswordResetService extends AbstractService<IPasswordReset, typeof paths> {
constructor() { constructor() {
super({}) super(paths)
this.paths = { this.paths = {
reset: '/user/password/reset', reset: '/user/password/reset',
requestReset: '/user/password/token', requestReset: '/user/password/token',

View File

@ -1,10 +1,12 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import type {IPasswordUpdate} from '@/modelTypes/IPasswordUpdate' import type {IPasswordUpdate} from '@/modelTypes/IPasswordUpdate'
export default class PasswordUpdateService extends AbstractService<IPasswordUpdate> { const paths = {
update: '/user/password',
} as const
export default class PasswordUpdateService extends AbstractService<IPasswordUpdate, typeof paths> {
constructor() { constructor() {
super({ super(paths)
update: '/user/password',
})
} }
} }

View File

@ -4,15 +4,17 @@ import type {IProject} from '@/modelTypes/IProject'
import TaskService from './task' import TaskService from './task'
import {colorFromHex} from '@/helpers/color/colorFromHex' import {colorFromHex} from '@/helpers/color/colorFromHex'
export default class ProjectService extends AbstractService<IProject> { const paths = {
create: '/namespaces/{namespaceId}/projects',
get: '/projects/{id}',
getAll: '/projects',
update: '/projects/{id}',
delete: '/projects/{id}',
} as const
export default class ProjectService extends AbstractService<IProject, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/namespaces/{namespaceId}/projects',
get: '/projects/{id}',
getAll: '/projects',
update: '/projects/{id}',
delete: '/projects/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,11 +2,13 @@ import AbstractService from './abstractService'
import projectDuplicateModel from '@/models/projectDuplicateModel' import projectDuplicateModel from '@/models/projectDuplicateModel'
import type {IProjectDuplicate} from '@/modelTypes/IProjectDuplicate' import type {IProjectDuplicate} from '@/modelTypes/IProjectDuplicate'
export default class ProjectDuplicateService extends AbstractService<IProjectDuplicate> { const paths = {
create: '/projects/{projectId}/duplicate',
} as const
export default class ProjectDuplicateService extends AbstractService<IProjectDuplicate, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/projects/{projectId}/duplicate',
})
} }
beforeCreate(model) { beforeCreate(model) {

View File

@ -1,11 +1,13 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import UserModel from '../models/user' import UserModel from '../models/user'
export default class ProjectUserService extends AbstractService { const paths = {
getAll: '/projects/{projectId}/projectusers',
} as const
export default class ProjectUserService extends AbstractService<UserModel, typeof paths> {
constructor() { constructor() {
super({ super(paths)
getAll: '/projects/{projectId}/projectusers',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -43,14 +43,16 @@ export function isSavedFilter(project: IProject) {
return getSavedFilterIdFromProjectId(project.id) > 0 return getSavedFilterIdFromProjectId(project.id) > 0
} }
export default class SavedFilterService extends AbstractService<ISavedFilter> { const paths = {
get: '/filters/{id}',
create: '/filters',
update: '/filters/{id}',
delete: '/filters/{id}',
} as const
export default class SavedFilterService extends AbstractService<ISavedFilter, typeof paths> {
constructor() { constructor() {
super({ super(paths)
get: '/filters/{id}',
create: '/filters',
update: '/filters/{id}',
delete: '/filters/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,12 +2,14 @@ import AbstractService from '@/services/abstractService'
import SubscriptionModel from '@/models/subscription' import SubscriptionModel from '@/models/subscription'
import type {ISubscription} from '@/modelTypes/ISubscription' import type {ISubscription} from '@/modelTypes/ISubscription'
export default class SubscriptionService extends AbstractService<ISubscription> { const paths = {
create: '/subscriptions/{entity}/{entityId}',
delete: '/subscriptions/{entity}/{entityId}',
} as const
export default class SubscriptionService extends AbstractService<ISubscription, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/subscriptions/{entity}/{entityId}',
delete: '/subscriptions/{entity}/{entityId}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -15,15 +15,17 @@ const parseDate = date => {
return null return null
} }
export default class TaskService extends AbstractService<ITask> { const paths = {
create: '/projects/{projectId}',
getAll: '/tasks/all',
get: '/tasks/{id}',
update: '/tasks/{id}',
delete: '/tasks/{id}',
} as const
export default class TaskService extends AbstractService<ITask, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/projects/{projectId}',
getAll: '/tasks/all',
get: '/tasks/{id}',
update: '/tasks/{id}',
delete: '/tasks/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,12 +2,14 @@ import AbstractService from './abstractService'
import TaskAssigneeModel from '@/models/taskAssignee' import TaskAssigneeModel from '@/models/taskAssignee'
import type {ITaskAssignee} from '@/modelTypes/ITaskAssignee' import type {ITaskAssignee} from '@/modelTypes/ITaskAssignee'
export default class TaskAssigneeService extends AbstractService<ITaskAssignee> { const paths = {
create: '/tasks/{taskId}/assignees',
delete: '/tasks/{taskId}/assignees/{userId}',
} as const
export default class TaskAssigneeService extends AbstractService<ITaskAssignee, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/tasks/{taskId}/assignees',
delete: '/tasks/{taskId}/assignees/{userId}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -14,11 +14,13 @@ export interface GetAllTasksParams {
filter_include_nulls: boolean, filter_include_nulls: boolean,
} }
export default class TaskCollectionService extends AbstractService<ITask> { const paths = {
getAll: '/projects/{projectId}/tasks',
} as const
export default class TaskCollectionService extends AbstractService<ITask, typeof paths> {
constructor() { constructor() {
super({ super(paths)
getAll: '/projects/{projectId}/tasks',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,15 +2,17 @@ import AbstractService from './abstractService'
import TaskCommentModel from '@/models/taskComment' import TaskCommentModel from '@/models/taskComment'
import type {ITaskComment} from '@/modelTypes/ITaskComment' import type {ITaskComment} from '@/modelTypes/ITaskComment'
export default class TaskCommentService extends AbstractService<ITaskComment> { const paths = {
create: '/tasks/{taskId}/comments',
getAll: '/tasks/{taskId}/comments',
get: '/tasks/{taskId}/comments/{id}',
update: '/tasks/{taskId}/comments/{id}',
delete: '/tasks/{taskId}/comments/{id}',
} as const
export default class TaskCommentService extends AbstractService<ITaskComment, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/tasks/{taskId}/comments',
getAll: '/tasks/{taskId}/comments',
get: '/tasks/{taskId}/comments/{id}',
update: '/tasks/{taskId}/comments/{id}',
delete: '/tasks/{taskId}/comments/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,12 +2,14 @@ import AbstractService from './abstractService'
import TaskRelationModel from '@/models/taskRelation' import TaskRelationModel from '@/models/taskRelation'
import type {ITaskRelation} from '@/modelTypes/ITaskRelation' import type {ITaskRelation} from '@/modelTypes/ITaskRelation'
export default class TaskRelationService extends AbstractService<ITaskRelation> { const paths = {
create: '/tasks/{taskId}/relations',
delete: '/tasks/{taskId}/relations/{relationKind}/{otherTaskId}',
} as const
export default class TaskRelationService extends AbstractService<ITaskRelation, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/tasks/{taskId}/relations',
delete: '/tasks/{taskId}/relations/{relationKind}/{otherTaskId}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,15 +2,17 @@ import AbstractService from './abstractService'
import TeamModel from '@/models/team' import TeamModel from '@/models/team'
import type {ITeam} from '@/modelTypes/ITeam' import type {ITeam} from '@/modelTypes/ITeam'
export default class TeamService extends AbstractService<ITeam> { const paths = {
create: '/teams',
get: '/teams/{id}',
getAll: '/teams',
update: '/teams/{id}',
delete: '/teams/{id}',
} as const
export default class TeamService extends AbstractService<ITeam, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/teams',
get: '/teams/{id}',
getAll: '/teams',
update: '/teams/{id}',
delete: '/teams/{id}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,13 +2,15 @@ import AbstractService from './abstractService'
import TeamMemberModel from '@/models/teamMember' import TeamMemberModel from '@/models/teamMember'
import type {ITeamMember} from '@/modelTypes/ITeamMember' import type {ITeamMember} from '@/modelTypes/ITeamMember'
export default class TeamMemberService extends AbstractService<ITeamMember> { const paths = {
create: '/teams/{teamId}/members',
delete: '/teams/{teamId}/members/{username}',
update: '/teams/{teamId}/members/{username}/admin',
} as const
export default class TeamMemberService extends AbstractService<ITeamMember, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/teams/{teamId}/members',
delete: '/teams/{teamId}/members/{username}',
update: '/teams/{teamId}/members/{username}/admin',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -3,14 +3,16 @@ import TeamNamespaceModel from '@/models/teamNamespace'
import type {ITeamNamespace} from '@/modelTypes/ITeamNamespace' import type {ITeamNamespace} from '@/modelTypes/ITeamNamespace'
import TeamModel from '@/models/team' import TeamModel from '@/models/team'
export default class TeamNamespaceService extends AbstractService<ITeamNamespace> { const paths = {
create: '/namespaces/{namespaceId}/teams',
getAll: '/namespaces/{namespaceId}/teams',
update: '/namespaces/{namespaceId}/teams/{teamId}',
delete: '/namespaces/{namespaceId}/teams/{teamId}',
} as const
export default class TeamNamespaceService extends AbstractService<ITeamNamespace, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/namespaces/{namespaceId}/teams',
getAll: '/namespaces/{namespaceId}/teams',
update: '/namespaces/{namespaceId}/teams/{teamId}',
delete: '/namespaces/{namespaceId}/teams/{teamId}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -3,14 +3,16 @@ import TeamProjectModel from '@/models/teamProject'
import type {ITeamProject} from '@/modelTypes/ITeamProject' import type {ITeamProject} from '@/modelTypes/ITeamProject'
import TeamModel from '@/models/team' import TeamModel from '@/models/team'
export default class TeamProjectService extends AbstractService<ITeamProject> { const paths = {
create: '/projects/{projectId}/teams',
getAll: '/projects/{projectId}/teams',
update: '/projects/{projectId}/teams/{teamId}',
delete: '/projects/{projectId}/teams/{teamId}',
} as const
export default class TeamProjectService extends AbstractService<ITeamProject, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/projects/{projectId}/teams',
getAll: '/projects/{projectId}/teams',
update: '/projects/{projectId}/teams/{teamId}',
delete: '/projects/{projectId}/teams/{teamId}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,13 +2,18 @@ import AbstractService from './abstractService'
import TotpModel from '@/models/totp' import TotpModel from '@/models/totp'
import type {ITotp} from '@/modelTypes/ITotp' import type {ITotp} from '@/modelTypes/ITotp'
export default class TotpService extends AbstractService<ITotp> { const urlPrefix = '/user/settings/totp' as const
urlPrefix = '/user/settings/totp' type Paths = {
get: typeof urlPrefix
}
export default class TotpService extends AbstractService<ITotp, Paths> {
urlPrefix = urlPrefix
constructor() { constructor() {
super({}) super({
get: urlPrefix,
this.paths.get = this.urlPrefix })
} }
modelFactory(data) { modelFactory(data) {

View File

@ -2,11 +2,13 @@ import AbstractService from './abstractService'
import UserModel from '@/models/user' import UserModel from '@/models/user'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class UserService extends AbstractService<IUser> { const paths = {
getAll: '/users',
} as const
export default class UserService extends AbstractService<IUser, typeof paths> {
constructor() { constructor() {
super({ super(paths)
getAll: '/users',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -3,14 +3,16 @@ import UserNamespaceModel from '@/models/userNamespace'
import type {IUserNamespace} from '@/modelTypes/IUserNamespace' import type {IUserNamespace} from '@/modelTypes/IUserNamespace'
import UserModel from '@/models/user' import UserModel from '@/models/user'
export default class UserNamespaceService extends AbstractService<IUserNamespace> { const paths = {
create: '/namespaces/{namespaceId}/users',
getAll: '/namespaces/{namespaceId}/users',
update: '/namespaces/{namespaceId}/users/{userId}',
delete: '/namespaces/{namespaceId}/users/{userId}',
} as const
export default class UserNamespaceService extends AbstractService<IUserNamespace, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/namespaces/{namespaceId}/users',
getAll: '/namespaces/{namespaceId}/users',
update: '/namespaces/{namespaceId}/users/{userId}',
delete: '/namespaces/{namespaceId}/users/{userId}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -3,14 +3,16 @@ import UserProjectModel from '@/models/userProject'
import type {IUserProject} from '@/modelTypes/IUserProject' import type {IUserProject} from '@/modelTypes/IUserProject'
import UserModel from '@/models/user' import UserModel from '@/models/user'
export default class UserProjectService extends AbstractService<IUserProject> { const paths = {
create: '/projects/{projectId}/users',
getAll: '/projects/{projectId}/users',
update: '/projects/{projectId}/users/{userId}',
delete: '/projects/{projectId}/users/{userId}',
} as const
export default class UserProjectService extends AbstractService<IUserProject, typeof paths> {
constructor() { constructor() {
super({ super(paths)
create: '/projects/{projectId}/users',
getAll: '/projects/{projectId}/users',
update: '/projects/{projectId}/users/{userId}',
delete: '/projects/{projectId}/users/{userId}',
})
} }
modelFactory(data) { modelFactory(data) {

View File

@ -1,10 +1,12 @@
import type {IUserSettings} from '@/modelTypes/IUserSettings' import type {IUserSettings} from '@/modelTypes/IUserSettings'
import AbstractService from './abstractService' import AbstractService from './abstractService'
export default class UserSettingsService extends AbstractService<IUserSettings> { const paths = {
update: '/user/settings/general',
} as const
export default class UserSettingsService extends AbstractService<IUserSettings, typeof paths> {
constructor() { constructor() {
super({ super(paths)
update: '/user/settings/general',
})
} }
} }