Move everything to models and services (#17)
This commit is contained in:
354
src/services/abstractService.js
Normal file
354
src/services/abstractService.js
Normal file
@ -0,0 +1,354 @@
|
||||
import axios from 'axios'
|
||||
import {reduce, replace} from 'lodash'
|
||||
|
||||
let config = require('../../public/config.json')
|
||||
|
||||
export default class AbstractService {
|
||||
|
||||
/////////////////////////////
|
||||
// Initial variable definitions
|
||||
///////////////////////////
|
||||
|
||||
http = null
|
||||
loading = false
|
||||
paths = {
|
||||
create: '',
|
||||
get: '',
|
||||
getAll: '',
|
||||
update: '',
|
||||
delete: '',
|
||||
}
|
||||
|
||||
/////////////
|
||||
// Service init
|
||||
///////////
|
||||
|
||||
/**
|
||||
* The abstract constructor.
|
||||
* @param paths An object with all paths. Default values are specified above.
|
||||
*/
|
||||
constructor(paths) {
|
||||
this.http = axios.create({
|
||||
baseURL: config.VIKUNJA_API_BASE_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// Set the default auth header if we have a token
|
||||
if (
|
||||
localStorage.getItem('token') !== '' &&
|
||||
localStorage.getItem('token') !== null &&
|
||||
localStorage.getItem('token') !== undefined
|
||||
) {
|
||||
this.http.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('token')
|
||||
}
|
||||
|
||||
this.paths = {
|
||||
create: paths.create !== undefined ? paths.create : '',
|
||||
get: paths.get !== undefined ? paths.get : '',
|
||||
getAll: paths.getAll !== undefined ? paths.getAll : '',
|
||||
update: paths.update !== undefined ? paths.update : '',
|
||||
delete: paths.delete !== undefined ? paths.delete : '',
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Global error handler
|
||||
///////////////////
|
||||
|
||||
/**
|
||||
* Handles the error and rejects the promise.
|
||||
* @param error
|
||||
* @returns {Promise<never>}
|
||||
*/
|
||||
errorHandler(error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Helper functions
|
||||
///////////////
|
||||
|
||||
/**
|
||||
* Returns an object with all route parameters and their values.
|
||||
* @param route
|
||||
* @returns object
|
||||
*/
|
||||
getRouteReplacements(route) {
|
||||
let parameters = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}
|
||||
let replace$$1 = {}
|
||||
let pattern = this.getRouteParameterPattern()
|
||||
pattern = new RegExp(pattern instanceof RegExp ? pattern.source : pattern, 'g')
|
||||
|
||||
for (let parameter; (parameter = pattern.exec(route)) !== null;) {
|
||||
replace$$1[parameter[0]] = parameters[parameter[1]];
|
||||
}
|
||||
|
||||
return replace$$1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the replacement pattern for url paths, can be overwritten by implementations.
|
||||
* @return {RegExp}
|
||||
*/
|
||||
getRouteParameterPattern() {
|
||||
return /{([^}]+)}/
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fully-ready-ready-to-make-a-request-to route with replaced parameters.
|
||||
* @param path
|
||||
* @param pathparams
|
||||
* @return string
|
||||
*/
|
||||
getReplacedRoute(path, pathparams) {
|
||||
let replacements = this.getRouteReplacements(path, pathparams)
|
||||
return reduce(replacements, function (result, value, parameter) {
|
||||
return replace(result, parameter, value)
|
||||
}, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* setLoading is a method which sets the loading variable to true, after a timeout of 100ms.
|
||||
* It has the timeout to prevent the loading indicator from showing for only a blink of an eye in the
|
||||
* case the api returns a response in < 100ms.
|
||||
* But because the timeout is created using setTimeout, it will still trigger even if the request is
|
||||
* already finished, so we return a method to call in that case.
|
||||
* @returns {Function}
|
||||
*/
|
||||
setLoading() {
|
||||
const timeout = setTimeout(() => {
|
||||
this.loading = true
|
||||
}, 100)
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// Default factories
|
||||
// It is possible to specify a factory for each type of request.
|
||||
// This makes it possible to have different models returned from different routes.
|
||||
// Specific factories for each request are completly optional, if these are not specified, the defautl factory is used.
|
||||
////////////////
|
||||
|
||||
/**
|
||||
* The modelFactory returns an model from an object.
|
||||
* This one here is the default one, usually the service definitions for a model will override this.
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
modelFactory(data) {
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the model factory for get requests.
|
||||
* @param data
|
||||
* @return {*}
|
||||
*/
|
||||
modelGetFactory(data) {
|
||||
return this.modelFactory(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the model factory for get all requests.
|
||||
* @param data
|
||||
* @return {*}
|
||||
*/
|
||||
modelGetAllFactory(data) {
|
||||
return this.modelFactory(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the model factory for create requests.
|
||||
* @param data
|
||||
* @return {*}
|
||||
*/
|
||||
modelCreateFactory(data) {
|
||||
return this.modelFactory(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the model factory for update requests.
|
||||
* @param data
|
||||
* @return {*}
|
||||
*/
|
||||
modelUpdateFactory(data) {
|
||||
return this.modelFactory(data)
|
||||
}
|
||||
|
||||
//////////////
|
||||
// Preprocessors
|
||||
////////////
|
||||
|
||||
/**
|
||||
* Default preprocessor for get requests
|
||||
* @param model
|
||||
* @return {*}
|
||||
*/
|
||||
beforeGet(model) {
|
||||
return model
|
||||
}
|
||||
|
||||
/**
|
||||
* Default preprocessor for create requests
|
||||
* @param model
|
||||
* @return {*}
|
||||
*/
|
||||
beforeCreate(model) {
|
||||
return model
|
||||
}
|
||||
|
||||
/**
|
||||
* Default preprocessor for update requests
|
||||
* @param model
|
||||
* @return {*}
|
||||
*/
|
||||
beforeUpdate(model) {
|
||||
return model
|
||||
}
|
||||
|
||||
/**
|
||||
* Default preprocessor for delete requests
|
||||
* @param model
|
||||
* @return {*}
|
||||
*/
|
||||
beforeDelete(model) {
|
||||
return model
|
||||
}
|
||||
|
||||
///////////////
|
||||
// Global actions
|
||||
/////////////
|
||||
|
||||
/**
|
||||
* Performs a get request to the url specified before.
|
||||
* @param model The model to use. The request path is built using the values from the model.
|
||||
* @param params Optional query parameters
|
||||
* @returns {Q.Promise<any>}
|
||||
*/
|
||||
get(model, params = {}) {
|
||||
if (this.paths.get === '') {
|
||||
return Promise.reject({message: 'This model is not able to get data.'})
|
||||
}
|
||||
|
||||
const cancel = this.setLoading()
|
||||
model = this.beforeGet(model)
|
||||
return this.http.get(this.getReplacedRoute(this.paths.get, model), {params: params})
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(this.modelGetFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a get request to the url specified before.
|
||||
* The difference between this and get() is this one is used to get a bunch of data (an array), not just a single object.
|
||||
* @param model The model to use. The request path is built using the values from the model.
|
||||
* @param params Optional query parameters
|
||||
* @returns {Q.Promise<any>}
|
||||
*/
|
||||
getAll(model = {}, params = {}) {
|
||||
if (this.paths.getAll === '') {
|
||||
return Promise.reject({message: 'This model is not able to get data.'})
|
||||
}
|
||||
|
||||
const cancel = this.setLoading()
|
||||
model = this.beforeGet(model)
|
||||
return this.http.get(this.getReplacedRoute(this.paths.getAll, model), {params: params})
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
if (Array.isArray(response.data)) {
|
||||
return Promise.resolve(response.data.map(entry => {
|
||||
return this.modelGetAllFactory(entry)
|
||||
}))
|
||||
}
|
||||
return Promise.resolve(this.modelGetAllFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a put request to the url specified before
|
||||
* @param model
|
||||
* @returns {Promise<any | never>}
|
||||
*/
|
||||
create(model) {
|
||||
if (this.paths.create === '') {
|
||||
return Promise.reject({message: 'This model is not able to create data.'})
|
||||
}
|
||||
|
||||
const cancel = this.setLoading()
|
||||
model = this.beforeCreate(model)
|
||||
return this.http.put(this.getReplacedRoute(this.paths.create, model), model)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(this.modelCreateFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a post request to the update url
|
||||
* @param model
|
||||
* @returns {Q.Promise<any>}
|
||||
*/
|
||||
update(model) {
|
||||
if (this.paths.update === '') {
|
||||
return Promise.reject({message: 'This model is not able to update data.'})
|
||||
}
|
||||
|
||||
const cancel = this.setLoading()
|
||||
model = this.beforeUpdate(model)
|
||||
return this.http.post(this.getReplacedRoute(this.paths.update, model), model)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(this.modelUpdateFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a delete request to the update url
|
||||
* @param model
|
||||
* @returns {Q.Promise<any>}
|
||||
*/
|
||||
delete(model) {
|
||||
if (this.paths.delete === '') {
|
||||
return Promise.reject({message: 'This model is not able to delete data.'})
|
||||
}
|
||||
|
||||
const cancel = this.setLoading()
|
||||
model = this.beforeUpdate(model)
|
||||
return this.http.delete(this.getReplacedRoute(this.paths.delete, model), model)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(response.data)
|
||||
})
|
||||
.finally(() => {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
26
src/services/list.js
Normal file
26
src/services/list.js
Normal file
@ -0,0 +1,26 @@
|
||||
import AbstractService from './abstractService'
|
||||
import ListModel from '../models/list'
|
||||
import TaskService from './task'
|
||||
|
||||
export default class ListService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/namespaces/{namespaceID}/lists',
|
||||
get: '/lists/{id}',
|
||||
update: '/lists/{id}',
|
||||
delete: '/lists/{id}',
|
||||
})
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new ListModel(data)
|
||||
}
|
||||
|
||||
beforeUpdate(model) {
|
||||
let taskService = new TaskService()
|
||||
model.tasks = model.tasks.map(task => {
|
||||
return taskService.beforeUpdate(task)
|
||||
})
|
||||
return model
|
||||
}
|
||||
}
|
18
src/services/namespace.js
Normal file
18
src/services/namespace.js
Normal file
@ -0,0 +1,18 @@
|
||||
import AbstractService from './abstractService'
|
||||
import NamespaceModel from '../models/namespace'
|
||||
|
||||
export default class NamespaceService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/namespaces',
|
||||
get: '/namespaces/{id}',
|
||||
getAll: '/namespaces',
|
||||
update: '/namespaces/{id}',
|
||||
delete: '/namespaces/{id}',
|
||||
});
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new NamespaceModel(data)
|
||||
}
|
||||
}
|
45
src/services/passwordReset.js
Normal file
45
src/services/passwordReset.js
Normal file
@ -0,0 +1,45 @@
|
||||
import AbstractService from './abstractService'
|
||||
import PasswordResetModel from '../models/passwordReset'
|
||||
|
||||
export default class PasswordResetService extends AbstractService {
|
||||
|
||||
constructor() {
|
||||
super({})
|
||||
this.paths = {
|
||||
reset: '/user/password/reset',
|
||||
requestReset: '/user/password/token',
|
||||
}
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new PasswordResetModel(data)
|
||||
}
|
||||
|
||||
resetPassword(model) {
|
||||
const cancel = this.setLoading()
|
||||
return this.http.post(this.paths.reset, model)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(this.modelFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
||||
requestResetPassword(model) {
|
||||
const cancel = this.setLoading()
|
||||
return this.http.post(this.paths.requestReset, model)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(this.modelFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
61
src/services/task.js
Normal file
61
src/services/task.js
Normal file
@ -0,0 +1,61 @@
|
||||
import AbstractService from './abstractService'
|
||||
import TaskModel from '../models/task'
|
||||
|
||||
export default class TaskService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/lists/{listID}',
|
||||
getAll: '/tasks/all/{sortBy}/{startDate}/{endDate}',
|
||||
update: '/tasks/{id}',
|
||||
delete: '/tasks/{id}',
|
||||
});
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new TaskModel(data)
|
||||
}
|
||||
|
||||
beforeUpdate(model) {
|
||||
// Convert the date in a unix timestamp
|
||||
model.dueDate = (+ new Date(model.dueDate)) / 1000
|
||||
model.startDate = (+ new Date(model.startDate)) / 1000
|
||||
model.endDate = (+ new Date(model.endDate)) / 1000
|
||||
|
||||
// remove all nulls, these would create empty reminders
|
||||
for (const index in model.reminderDates) {
|
||||
if (model.reminderDates[index] === null) {
|
||||
model.reminderDates.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Make normal timestamps from js dates
|
||||
model.reminderDates = model.reminderDates.map(r => {
|
||||
return Math.round(r / 1000)
|
||||
})
|
||||
|
||||
// Make the repeating amount to seconds
|
||||
let repeatAfterSeconds = 0
|
||||
if (model.repeatAfter.amount !== null || model.repeatAfter.amount !== 0) {
|
||||
switch (model.repeatAfter.type) {
|
||||
case 'hours':
|
||||
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60
|
||||
break
|
||||
case 'days':
|
||||
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24
|
||||
break
|
||||
case 'weeks':
|
||||
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 7
|
||||
break
|
||||
case 'months':
|
||||
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 30
|
||||
break
|
||||
case 'years':
|
||||
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 365
|
||||
break
|
||||
}
|
||||
}
|
||||
model.repeatAfter = repeatAfterSeconds
|
||||
|
||||
return model
|
||||
}
|
||||
}
|
18
src/services/team.js
Normal file
18
src/services/team.js
Normal file
@ -0,0 +1,18 @@
|
||||
import AbstractService from './abstractService'
|
||||
import TeamModel from '../models/team'
|
||||
|
||||
export default class TeamService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/teams',
|
||||
get: '/teams/{id}',
|
||||
getAll: '/teams',
|
||||
update: '/teams/{id}',
|
||||
delete: '/teams/{id}',
|
||||
});
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new TeamModel(data)
|
||||
}
|
||||
}
|
22
src/services/teamList.js
Normal file
22
src/services/teamList.js
Normal file
@ -0,0 +1,22 @@
|
||||
import AbstractService from './abstractService'
|
||||
import TeamListModel from '../models/teamList'
|
||||
import TeamModel from '../models/team'
|
||||
|
||||
export default class TeamListService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/lists/{listID}/teams',
|
||||
getAll: '/lists/{listID}/teams',
|
||||
update: '/lists/{listID}/teams/{teamID}',
|
||||
delete: '/lists/{listID}/teams/{teamID}',
|
||||
})
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new TeamListModel(data)
|
||||
}
|
||||
|
||||
modelGetAllFactory(data) {
|
||||
return new TeamModel(data)
|
||||
}
|
||||
}
|
21
src/services/teamMember.js
Normal file
21
src/services/teamMember.js
Normal file
@ -0,0 +1,21 @@
|
||||
import AbstractService from './abstractService'
|
||||
import TeamMemberModel from '../models/teamMember'
|
||||
|
||||
export default class TeamMemberService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/teams/{teamID}/members',
|
||||
delete: '/teams/{teamID}/members/{id}', // "id" is the user id because we're intheriting from a normal user
|
||||
});
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new TeamMemberModel(data)
|
||||
}
|
||||
|
||||
beforeCreate(model) {
|
||||
model.userID = model.id // The api wants to get the user id as userID
|
||||
model.admin = model.admin === null ? false : model.admin
|
||||
return model
|
||||
}
|
||||
}
|
22
src/services/teamNamespace.js
Normal file
22
src/services/teamNamespace.js
Normal file
@ -0,0 +1,22 @@
|
||||
import AbstractService from './abstractService'
|
||||
import TeamNamespaceModel from '../models/teamNamespace'
|
||||
import TeamModel from '../models/team'
|
||||
|
||||
export default class TeamNamespaceService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/namespaces/{namespaceID}/teams',
|
||||
getAll: '/namespaces/{namespaceID}/teams',
|
||||
update: '/namespaces/{namespaceID}/teams/{teamID}',
|
||||
delete: '/namespaces/{namespaceID}/teams/{teamID}',
|
||||
})
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new TeamNamespaceModel(data)
|
||||
}
|
||||
|
||||
modelGetAllFactory(data) {
|
||||
return new TeamModel(data)
|
||||
}
|
||||
}
|
14
src/services/user.js
Normal file
14
src/services/user.js
Normal file
@ -0,0 +1,14 @@
|
||||
import AbstractService from './abstractService'
|
||||
import UserModel from '../models/user'
|
||||
|
||||
export default class UserService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
getAll: '/users'
|
||||
})
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new UserModel(data)
|
||||
}
|
||||
}
|
22
src/services/userList.js
Normal file
22
src/services/userList.js
Normal file
@ -0,0 +1,22 @@
|
||||
import AbstractService from './abstractService'
|
||||
import UserListModel from '../models/userList'
|
||||
import UserModel from '../models/user'
|
||||
|
||||
export default class UserListService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/lists/{listID}/users',
|
||||
getAll: '/lists/{listID}/users',
|
||||
update: '/lists/{listID}/users/{userID}',
|
||||
delete: '/lists/{listID}/users/{userID}',
|
||||
})
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new UserListModel(data)
|
||||
}
|
||||
|
||||
modelGetAllFactory(data) {
|
||||
return new UserModel(data)
|
||||
}
|
||||
}
|
22
src/services/userNamespace.js
Normal file
22
src/services/userNamespace.js
Normal file
@ -0,0 +1,22 @@
|
||||
import AbstractService from './abstractService'
|
||||
import UserNamespaceModel from '../models/userNamespace'
|
||||
import UserModel from '../models/user'
|
||||
|
||||
export default class UserNamespaceService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/namespaces/{namespaceID}/users',
|
||||
getAll: '/namespaces/{namespaceID}/users',
|
||||
update: '/namespaces/{namespaceID}/users/{userID}',
|
||||
delete: '/namespaces/{namespaceID}/users/{userID}',
|
||||
})
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new UserNamespaceModel(data)
|
||||
}
|
||||
|
||||
modelGetAllFactory(data) {
|
||||
return new UserModel(data)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user