1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-23 15:19:44 +02:00

Initial commit

This commit is contained in:
Maksim Eltyshev 2019-08-31 04:07:25 +05:00
commit 36fe34e8e1
583 changed files with 91539 additions and 0 deletions

88
client/src/models/Action.js Executable file
View file

@ -0,0 +1,88 @@
import { Model, attr, fk } from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'Action';
static fields = {
id: attr(),
type: attr(),
data: attr(),
createdAt: attr({
getDefault: () => new Date(),
}),
isInCard: attr({
getDefault: () => true,
}),
cardId: fk({
to: 'Card',
as: 'card',
relatedName: 'actions',
}),
userId: fk({
to: 'User',
as: 'user',
relatedName: 'actions',
}),
};
static reducer({ type, payload }, Action) {
switch (type) {
case ActionTypes.ACTIONS_FETCH_SUCCEEDED:
payload.actions.forEach((action) => {
Action.upsert(action);
});
break;
case ActionTypes.ACTION_CREATE_RECEIVED:
case ActionTypes.COMMENT_ACTION_CREATE:
Action.upsert(payload.action);
break;
case ActionTypes.ACTION_UPDATE_RECEIVED:
Action.withId(payload.action.id).update(payload.action);
break;
case ActionTypes.ACTION_DELETE_RECEIVED:
Action.withId(payload.action.id).delete();
break;
case ActionTypes.COMMENT_ACTION_UPDATE:
Action.withId(payload.id).update({
data: payload.data,
});
break;
case ActionTypes.COMMENT_ACTION_DELETE:
Action.withId(payload.id).delete();
break;
case ActionTypes.COMMENT_ACTION_CREATE_SUCCEEDED:
Action.withId(payload.localId).delete();
Action.upsert(payload.action);
break;
case ActionTypes.NOTIFICATIONS_FETCH_SUCCEEDED:
payload.actions.forEach((action) => {
Action.upsert({
...action,
isInCard: false,
});
});
break;
case ActionTypes.NOTIFICATION_CREATE_RECEIVED: {
const actionModel = Action.withId(payload.action.id);
Action.upsert({
...payload.action,
isInCard: actionModel ? actionModel.isInCard : false,
});
break;
}
default:
}
}
}

109
client/src/models/Board.js Executable file
View file

@ -0,0 +1,109 @@
import {
Model, attr, fk, many,
} from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'Board';
static fields = {
id: attr(),
position: attr(),
name: attr(),
isFetching: attr({
getDefault: () => null,
}),
projectId: fk({
to: 'Project',
as: 'project',
relatedName: 'boards',
}),
filterUsers: many('User', 'filterBoards'),
filterLabels: many('Label', 'filterBoards'),
};
static reducer({ type, payload }, Board) {
switch (type) {
case ActionTypes.USER_TO_BOARD_FILTER_ADD:
Board.withId(payload.boardId).filterUsers.add(payload.id);
break;
case ActionTypes.USER_FROM_BOARD_FILTER_REMOVE:
Board.withId(payload.boardId).filterUsers.remove(payload.id);
break;
case ActionTypes.PROJECTS_FETCH_SUCCEEDED:
case ActionTypes.PROJECT_CREATE_SUCCEEDED:
case ActionTypes.PROJECT_CREATE_RECEIVED:
payload.boards.forEach((board) => {
Board.upsert(board);
});
break;
case ActionTypes.BOARD_CREATE:
case ActionTypes.BOARD_CREATE_RECEIVED:
Board.upsert(payload.board);
break;
case ActionTypes.BOARD_UPDATE:
Board.withId(payload.id).update(payload.data);
break;
case ActionTypes.BOARD_DELETE:
Board.withId(payload.id).deleteWithRelated();
break;
case ActionTypes.BOARD_CREATE_SUCCEEDED:
Board.withId(payload.localId).delete();
Board.upsert({
...payload.board,
isFetching: false,
});
break;
case ActionTypes.BOARD_FETCH_REQUESTED:
Board.withId(payload.id).update({
isFetching: true,
});
break;
case ActionTypes.BOARD_FETCH_SUCCEEDED:
Board.withId(payload.board.id).update({
...payload.board,
isFetching: false,
});
break;
case ActionTypes.BOARD_UPDATE_RECEIVED:
Board.withId(payload.board.id).update(payload.board);
break;
case ActionTypes.BOARD_DELETE_RECEIVED:
Board.withId(payload.board.id).deleteWithRelated();
break;
case ActionTypes.LABEL_TO_BOARD_FILTER_ADD:
Board.withId(payload.boardId).filterLabels.add(payload.id);
break;
case ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE:
Board.withId(payload.boardId).filterLabels.remove(payload.id);
break;
default:
}
}
getOrderedListsQuerySet() {
return this.lists.orderBy('position');
}
deleteWithRelated() {
this.cards.toModelArray().forEach((cardModel) => {
cardModel.deleteWithRelated();
});
this.delete();
}
}

166
client/src/models/Card.js Executable file
View file

@ -0,0 +1,166 @@
import {
Model, attr, fk, many,
} from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
import Config from '../constants/Config';
export default class extends Model {
static modelName = 'Card';
static fields = {
id: attr(),
position: attr(),
name: attr(),
description: attr(),
deadline: attr(),
timer: attr(),
isSubscribed: attr({
getDefault: () => false,
}),
isActionsFetching: attr({
getDefault: () => false,
}),
isAllActionsFetched: attr({
getDefault: () => false,
}),
listId: fk({
to: 'List',
as: 'list',
relatedName: 'cards',
}),
boardId: fk({
to: 'Board',
as: 'board',
relatedName: 'cards',
}),
users: many('User', 'cards'),
labels: many('Label', 'cards'),
};
static reducer({ type, payload }, Card) {
switch (type) {
case ActionTypes.USER_TO_CARD_ADD: {
const cardModel = Card.withId(payload.cardId);
cardModel.users.add(payload.id);
if (payload.isCurrent) {
cardModel.isSubscribed = true;
}
break;
}
case ActionTypes.USER_FROM_CARD_REMOVE:
Card.withId(payload.cardId).users.remove(payload.id);
break;
case ActionTypes.BOARD_FETCH_SUCCEEDED:
payload.cards.forEach((card) => {
Card.upsert(card);
});
payload.cardMemberships.forEach(({ cardId, userId }) => {
Card.withId(cardId).users.add(userId);
});
payload.cardLabels.forEach(({ cardId, labelId }) => {
Card.withId(cardId).labels.add(labelId);
});
break;
case ActionTypes.LABEL_TO_CARD_ADD:
Card.withId(payload.cardId).labels.add(payload.id);
break;
case ActionTypes.LABEL_FROM_CARD_REMOVE:
Card.withId(payload.cardId).labels.remove(payload.id);
break;
case ActionTypes.CARD_CREATE:
case ActionTypes.CARD_CREATE_RECEIVED:
case ActionTypes.CARD_FETCH_SUCCEEDED:
case ActionTypes.NOTIFICATION_CREATE_RECEIVED:
Card.upsert(payload.card);
break;
case ActionTypes.CARD_UPDATE:
Card.withId(payload.id).update(payload.data);
break;
case ActionTypes.CARD_DELETE:
Card.withId(payload.id).deleteWithRelated();
break;
case ActionTypes.CARD_CREATE_SUCCEEDED:
Card.withId(payload.localId).delete();
Card.upsert(payload.card);
break;
case ActionTypes.CARD_UPDATE_RECEIVED:
Card.withId(payload.card.id).update(payload.card);
break;
case ActionTypes.CARD_DELETE_RECEIVED:
Card.withId(payload.card.id).deleteWithRelated();
break;
case ActionTypes.CARD_MEMBERSHIP_CREATE_RECEIVED:
Card.withId(payload.cardMembership.cardId).users.add(payload.cardMembership.userId);
break;
case ActionTypes.CARD_MEMBERSHIP_DELETE_RECEIVED:
Card.withId(payload.cardMembership.cardId).users.remove(payload.cardMembership.userId);
break;
case ActionTypes.CARD_LABEL_CREATE_RECEIVED:
Card.withId(payload.cardLabel.cardId).labels.add(payload.cardLabel.labelId);
break;
case ActionTypes.CARD_LABEL_DELETE_RECEIVED:
Card.withId(payload.cardLabel.cardId).labels.remove(payload.cardLabel.labelId);
break;
case ActionTypes.ACTIONS_FETCH_REQUESTED:
Card.withId(payload.cardId).update({
isActionsFetching: true,
});
break;
case ActionTypes.ACTIONS_FETCH_SUCCEEDED:
Card.withId(payload.cardId).update({
isActionsFetching: false,
isAllActionsFetched: payload.actions.length < Config.ACTIONS_LIMIT,
});
break;
case ActionTypes.NOTIFICATIONS_FETCH_SUCCEEDED:
payload.cards.forEach((card) => {
Card.upsert(card);
});
break;
default:
}
}
getOrderedTasksQuerySet() {
return this.tasks.orderBy('id');
}
getOrderedInCardActionsQuerySet() {
return this.actions.orderBy('id', 'desc');
}
getUnreadNotificationsQuerySet() {
return this.notifications.filter({
isRead: false,
});
}
deleteWithRelated() {
this.tasks.delete();
this.actions.delete();
this.delete();
}
}

58
client/src/models/Label.js Executable file
View file

@ -0,0 +1,58 @@
import { Model, attr, fk } from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'Label';
static fields = {
id: attr(),
name: attr(),
color: attr(),
boardId: fk({
to: 'Board',
as: 'board',
relatedName: 'labels',
}),
};
static reducer({ type, payload }, Label) {
switch (type) {
case ActionTypes.BOARD_CREATE_SUCCEEDED:
case ActionTypes.BOARD_CREATE_RECEIVED:
case ActionTypes.BOARD_FETCH_SUCCEEDED:
payload.labels.forEach((label) => {
Label.upsert(label);
});
break;
case ActionTypes.LABEL_CREATE:
case ActionTypes.LABEL_CREATE_RECEIVED:
Label.upsert(payload.label);
break;
case ActionTypes.LABEL_UPDATE:
Label.withId(payload.id).update(payload.data);
break;
case ActionTypes.LABEL_DELETE:
Label.withId(payload.id).delete();
break;
case ActionTypes.LABEL_CREATE_SUCCEEDED:
Label.withId(payload.localId).delete();
Label.upsert(payload.label);
break;
case ActionTypes.LABEL_UPDATE_RECEIVED:
Label.withId(payload.label.id).update(payload.label);
break;
case ActionTypes.LABEL_DELETE_RECEIVED:
Label.withId(payload.label.id).delete();
break;
default:
}
}
}

95
client/src/models/List.js Executable file
View file

@ -0,0 +1,95 @@
import { Model, attr, fk } from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'List';
static fields = {
id: attr(),
position: attr(),
name: attr(),
boardId: fk({
to: 'Board',
as: 'board',
relatedName: 'lists',
}),
};
static reducer({ type, payload }, List) {
switch (type) {
case ActionTypes.BOARD_CREATE_SUCCEEDED:
case ActionTypes.BOARD_CREATE_RECEIVED:
case ActionTypes.BOARD_FETCH_SUCCEEDED:
payload.lists.forEach((list) => {
List.upsert(list);
});
break;
case ActionTypes.LIST_CREATE:
case ActionTypes.LIST_CREATE_RECEIVED:
List.upsert(payload.list);
break;
case ActionTypes.LIST_UPDATE:
List.withId(payload.id).update(payload.data);
break;
case ActionTypes.LIST_DELETE:
List.withId(payload.id).deleteWithRelated();
break;
case ActionTypes.LIST_CREATE_SUCCEEDED:
List.withId(payload.localId).delete();
List.upsert(payload.list);
break;
case ActionTypes.LIST_UPDATE_RECEIVED:
List.withId(payload.list.id).update(payload.list);
break;
case ActionTypes.LIST_DELETE_RECEIVED:
List.withId(payload.list.id).deleteWithRelated();
break;
default:
}
}
getOrderedCardsQuerySet() {
return this.cards.orderBy('position');
}
getOrderedFilteredCardsModelArray() {
let cardModels = this.getOrderedCardsQuerySet().toModelArray();
const filterUserIds = this.board.filterUsers.toRefArray().map((user) => user.id);
const filterLabelIds = this.board.filterLabels.toRefArray().map((label) => label.id);
if (filterUserIds.length > 0) {
cardModels = cardModels.filter((cardModel) => {
const users = cardModel.users.toRefArray();
return users.some((user) => filterUserIds.includes(user.id));
});
}
if (filterLabelIds.length > 0) {
cardModels = cardModels.filter((cardModel) => {
const labels = cardModel.labels.toRefArray();
return labels.some((label) => filterLabelIds.includes(label.id));
});
}
return cardModels;
}
deleteWithRelated() {
this.cards.toModelArray().forEach((cardModel) => {
cardModel.deleteWithRelated();
});
this.delete();
}
}

View file

@ -0,0 +1,60 @@
import { Model, attr, fk } from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'Notification';
static fields = {
id: attr(),
type: attr(),
data: attr(),
isRead: attr(),
userId: fk({
to: 'User',
as: 'user',
relatedName: 'notifications',
}),
actionId: fk({
to: 'Action',
as: 'action',
relatedName: 'notifications',
}),
cardId: fk({
to: 'Card',
as: 'card',
relatedName: 'notifications',
}),
};
static reducer({ type, payload }, Notification) {
switch (type) {
case ActionTypes.NOTIFICATIONS_DELETE:
payload.ids.forEach((id) => {
Notification.withId(id).delete();
});
break;
case ActionTypes.NOTIFICATIONS_FETCH_SUCCEEDED:
payload.notifications.forEach((notification) => {
Notification.upsert(notification);
});
break;
case ActionTypes.NOTIFICATION_CREATE_RECEIVED:
Notification.upsert(payload.notification);
break;
case ActionTypes.NOTIFICATION_DELETE_RECEIVED: {
const notificationModel = Notification.withId(payload.notification.id);
if (notificationModel) {
notificationModel.delete();
}
break;
}
default:
}
}
}

66
client/src/models/Project.js Executable file
View file

@ -0,0 +1,66 @@
import { Model, attr, many } from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'Project';
static fields = {
id: attr(),
name: attr(),
users: many({
to: 'User',
through: 'ProjectMembership',
relatedName: 'projects',
}),
};
static reducer({ type, payload }, Project) {
switch (type) {
case ActionTypes.PROJECTS_FETCH_SUCCEEDED:
payload.projects.forEach((project) => {
Project.upsert(project);
});
break;
case ActionTypes.PROJECT_UPDATE:
Project.withId(payload.id).update(payload.data);
break;
case ActionTypes.PROJECT_DELETE:
Project.withId(payload.id).deleteWithRelated();
break;
case ActionTypes.PROJECT_CREATE_SUCCEEDED:
case ActionTypes.PROJECT_CREATE_RECEIVED:
Project.upsert(payload.project);
break;
case ActionTypes.PROJECT_UPDATE_RECEIVED:
Project.withId(payload.project.id).update(payload.project);
break;
case ActionTypes.PROJECT_DELETE_RECEIVED:
Project.withId(payload.project.id).deleteWithRelated();
break;
default:
}
}
getOrderedMembershipsQuerySet() {
return this.memberships.orderBy('id');
}
getOrderedBoardsQuerySet() {
return this.boards.orderBy('position');
}
deleteWithRelated() {
this.boards.toModelArray().forEach((boardModel) => {
boardModel.deleteWithRelated();
});
this.delete();
}
}

View file

@ -0,0 +1,69 @@
import { Model, attr, fk } from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'ProjectMembership';
static fields = {
id: attr(),
projectId: fk({
to: 'Project',
as: 'project',
relatedName: 'memberships',
}),
userId: fk({
to: 'User',
as: 'user',
relatedName: 'projectMemberships',
}),
};
static reducer({ type, payload }, ProjectMembership) {
switch (type) {
case ActionTypes.PROJECTS_FETCH_SUCCEEDED:
case ActionTypes.PROJECT_CREATE_SUCCEEDED:
case ActionTypes.PROJECT_CREATE_RECEIVED:
payload.projectMemberships.forEach((projectMembership) => {
ProjectMembership.upsert(projectMembership);
});
break;
case ActionTypes.PROJECT_MEMBERSHIP_CREATE:
case ActionTypes.PROJECT_MEMBERSHIP_CREATE_RECEIVED:
ProjectMembership.upsert(payload.projectMembership);
break;
case ActionTypes.PROJECT_MEMBERSHIP_CREATE_SUCCEEDED:
ProjectMembership.withId(payload.localId).delete();
ProjectMembership.upsert(payload.projectMembership);
break;
case ActionTypes.PROJECT_MEMBERSHIP_DELETE:
ProjectMembership.withId(payload.id).deleteWithRelated();
break;
case ActionTypes.PROJECT_MEMBERSHIP_DELETE_RECEIVED:
ProjectMembership.withId(payload.projectMembership.id).deleteWithRelated();
break;
default:
}
}
deleteWithRelated() {
this.project.boards.toModelArray().forEach((boardModel) => {
boardModel.cards.toModelArray().forEach((cardModel) => {
try {
cardModel.users.remove(this.userId);
} catch {} // eslint-disable-line no-empty
});
try {
boardModel.filterUsers.remove(this.userId);
} catch {} // eslint-disable-line no-empty
});
this.delete();
}
}

58
client/src/models/Task.js Executable file
View file

@ -0,0 +1,58 @@
import { Model, attr, fk } from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'Task';
static fields = {
id: attr(),
name: attr(),
isCompleted: attr({
getDefault: () => false,
}),
cardId: fk({
to: 'Card',
as: 'card',
relatedName: 'tasks',
}),
};
static reducer({ type, payload }, Task) {
switch (type) {
case ActionTypes.BOARD_FETCH_SUCCEEDED:
payload.tasks.forEach((task) => {
Task.upsert(task);
});
break;
case ActionTypes.TASK_CREATE:
case ActionTypes.TASK_CREATE_RECEIVED:
Task.upsert(payload.task);
break;
case ActionTypes.TASK_UPDATE:
Task.withId(payload.id).update(payload.data);
break;
case ActionTypes.TASK_DELETE:
Task.withId(payload.id).delete();
break;
case ActionTypes.TASK_CREATE_SUCCEEDED:
Task.withId(payload.localId).delete();
Task.upsert(payload.task);
break;
case ActionTypes.TASK_UPDATE_RECEIVED:
Task.withId(payload.task.id).update(payload.task);
break;
case ActionTypes.TASK_DELETE_RECEIVED:
Task.withId(payload.task.id).delete();
break;
default:
}
}
}

112
client/src/models/User.js Executable file
View file

@ -0,0 +1,112 @@
import { Model, attr } from 'redux-orm';
import ActionTypes from '../constants/ActionTypes';
export default class extends Model {
static modelName = 'User';
static fields = {
id: attr(),
email: attr(),
name: attr(),
avatar: attr(),
deletedAt: attr(),
isAdmin: attr({
getDefault: () => false,
}),
isAvatarUploading: attr({
getDefault: () => false,
}),
};
static reducer({ type, payload }, User) {
switch (type) {
case ActionTypes.USER_CREATE_SUCCEEDED:
case ActionTypes.USER_CREATE_RECEIVED:
case ActionTypes.CURRENT_USER_FETCH_SUCCEEDED:
case ActionTypes.PROJECT_MEMBERSHIP_CREATE_RECEIVED:
case ActionTypes.NOTIFICATION_CREATE_RECEIVED:
User.upsert(payload.user);
break;
case ActionTypes.USERS_FETCH_SUCCEEDED:
case ActionTypes.PROJECTS_FETCH_SUCCEEDED:
case ActionTypes.PROJECT_CREATE_SUCCEEDED:
case ActionTypes.PROJECT_CREATE_RECEIVED:
case ActionTypes.ACTIONS_FETCH_SUCCEEDED:
case ActionTypes.NOTIFICATIONS_FETCH_SUCCEEDED:
payload.users.forEach((user) => {
User.upsert(user);
});
break;
case ActionTypes.USER_UPDATE:
User.withId(payload.id).update(payload.data);
break;
case ActionTypes.USER_DELETE:
User.withId(payload.id).deleteWithRelated();
break;
case ActionTypes.USER_UPDATE_RECEIVED:
User.withId(payload.user.id).update(payload.user);
break;
case ActionTypes.USER_AVATAR_UPLOAD_REQUESTED:
User.withId(payload.id).update({
isAvatarUploading: true,
});
break;
case ActionTypes.USER_AVATAR_UPLOAD_SUCCEEDED:
User.withId(payload.user.id).update({
...payload.user,
isAvatarUploading: false,
});
break;
case ActionTypes.USER_AVATAR_UPLOAD_FAILED:
User.withId(payload.id).update({
isAvatarUploading: false,
});
break;
case ActionTypes.USER_DELETE_SUCCEEDED:
case ActionTypes.USER_DELETE_RECEIVED:
User.withId(payload.user.id).deleteWithRelated(payload.user);
break;
default:
}
}
static getOrderedUndeletedQuerySet() {
return this.filter({
deletedAt: null,
}).orderBy('id');
}
getOrderedProjectMembershipsQuerySet() {
return this.projectMemberships.orderBy('id');
}
getOrderedUnreadNotificationsQuerySet() {
return this.notifications
.filter({
isRead: false,
})
.orderBy('id', 'desc');
}
deleteWithRelated(user) {
this.projectMemberships.toModelArray().forEach((projectMembershipModel) => {
projectMembershipModel.deleteWithRelated();
});
this.update(
user || {
deletedAt: new Date(),
},
);
}
}

14
client/src/models/index.js Executable file
View file

@ -0,0 +1,14 @@
import User from './User';
import Project from './Project';
import ProjectMembership from './ProjectMembership';
import Board from './Board';
import List from './List';
import Label from './Label';
import Card from './Card';
import Task from './Task';
import Action from './Action';
import Notification from './Notification';
export {
User, Project, ProjectMembership, Board, List, Label, Card, Task, Action, Notification,
};