Add differentiation between media and text posts

Add type argument when creating posts. If the post is of type MEDIA it will be invisible until an image was uploaded for the post.
pull/4/head
trivernis 5 years ago
parent 8dc1424775
commit 6ede632507

@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Upload handling for media entries (via /upload)
- routine to cleanup orphaned media entries (not referenced by post, user, group)
- delete handler for media to delete the corresponding file
- type for create post to know if it is a media or text post (media posts are invisible until a media file is uploaded)
### Removed

@ -179,6 +179,9 @@ namespace dataaccess {
limit: first,
offset,
order: [["createdAt", "DESC"]],
where: {
visible: true,
},
});
} else {
// more performant way to get the votes with plain sql
@ -206,15 +209,17 @@ namespace dataaccess {
* @param content
* @param authorId
* @param activityId
* @param type
*/
export async function createPost(content: string, authorId: number, activityId?: number): Promise<models.Post> {
export async function createPost(content: string, authorId: number, activityId?: number,
type: PostType = PostType.TEXT): Promise<models.Post> {
const blacklisted = await checkBlacklisted(content);
if (blacklisted.length > 0) {
throw new BlacklistedError(blacklisted.map((p) => p.phrase), "content");
}
const activity = await models.Activity.findByPk(activityId);
if (!activityId || activity) {
const post = await models.Post.create({content, authorId, activityId});
const post = await models.Post.create({content, authorId, activityId, visible: type !== PostType.MEDIA});
globals.internalEmitter.emit(InternalEvents.POSTCREATE, post);
if (activity) {
const user = await models.User.findByPk(authorId);
@ -236,11 +241,15 @@ namespace dataaccess {
const post = await models.Post.findByPk(postId, {include: [{model: Activity}, {association: "rAuthor"}]});
const activity = await post.activity();
const author = await post.author();
const media = await post.$get("rMedia") as models.Media;
if (activity && author) {
author.rankpoints -= activity.points;
await author.save();
}
await post.destroy();
if (media) {
await media.destroy();
}
} catch (err) {
globals.logger.error(err.message);
globals.logger.debug(err.stack);
@ -417,6 +426,17 @@ namespace dataaccess {
NEW = "NEW",
}
/**
* The type of the post
*/
export enum PostType {
TEXT = "TEXT",
MEDIA = "MEDIA",
}
/**
* Enum representing the type of membership change for the membership change function
*/
export enum MembershipChangeAction {
ADD,
REMOVE,

@ -14,8 +14,8 @@ export class Media extends Model<Media> {
* @param instance
*/
@BeforeDestroy
public static deleteMediaFile(instance: Media) {
fsx.unlinkSync(instance.path);
public static async deleteMediaFile(instance: Media) {
await fsx.unlink(instance.path);
}
/**

@ -31,6 +31,13 @@ export class Post extends Model<Post> {
@Column({type: sqz.STRING(2048), allowNull: false})
public content: string;
/**
* If the post is publically visible
*/
@NotNull
@Column({defaultValue: true, allowNull: false})
public visible: boolean;
/**
* The id of the post author
*/

@ -305,11 +305,15 @@ export class User extends Model<User> {
* a list of posts the user has created
* @param first
* @param offset
* @param request
*/
public async posts({first, offset}: { first: number, offset: number }): Promise<Post[]> {
public async posts({first, offset}: { first: number, offset: number }, request: any): Promise<Post[]> {
const limit = first ?? 10;
offset = offset ?? 0;
return await this.$get("rPosts", {limit, offset}) as Post[];
if (request.session.userId === this.getDataValue("id")) {
return await this.$get("rPosts", { limit, offset, order: [["id", "desc"]]}) as Post[];
}
return await this.$get("rPosts", { limit, offset, where: {visible: true}, order: [["id", "desc"]]}) as Post[];
}
/**

@ -216,6 +216,8 @@ export class UploadRoute extends Route {
}
if (media) {
await post.$set("rMedia", media);
post.visible = true;
await post.save();
fileName = media.url;
success = true;
}

@ -2,7 +2,7 @@ import {GraphQLError} from "graphql";
import {FileUpload} from "graphql-upload";
import * as yaml from "js-yaml";
import isEmail from "validator/lib/isEmail";
import dataaccess from "../../lib/dataAccess";
import dataAccess from "../../lib/dataAccess";
import {BlacklistedError} from "../../lib/errors/BlacklistedError";
import {GroupNotFoundError} from "../../lib/errors/GroupNotFoundError";
import {InvalidEmailError} from "../../lib/errors/InvalidEmailError";
@ -53,7 +53,7 @@ export class MutationResolver extends BaseResolver {
* @param request
*/
public async login({email, passwordHash}: { email: string, passwordHash: string }, request: any): Promise<User> {
const user = await dataaccess.getUserByLogin(email, passwordHash);
const user = await dataAccess.getUserByLogin(email, passwordHash);
request.session.userId = user.id;
return user;
}
@ -96,7 +96,7 @@ export class MutationResolver extends BaseResolver {
if (!mailValid) {
throw new InvalidEmailError(email);
}
const user = await dataaccess.registerUser(username, email, passwordHash);
const user = await dataAccess.registerUser(username, email, passwordHash);
request.session.userId = user.id;
return user;
}
@ -124,8 +124,8 @@ export class MutationResolver extends BaseResolver {
* @param type
* @param request
*/
public async vote({postId, type}: { postId: number, type: dataaccess.VoteType }, request: any):
Promise<{ post: Post, voteType: dataaccess.VoteType }> {
public async vote({postId, type}: { postId: number, type: dataAccess.VoteType }, request: any):
Promise<{ post: Post, voteType: dataAccess.VoteType }> {
this.ensureLoggedIn(request);
const post = await Post.findByPk(postId);
if (post) {
@ -143,15 +143,17 @@ export class MutationResolver extends BaseResolver {
* Creates a new post
* @param content
* @param activityId
* @param type
* @param request
*/
public async createPost({content, activityId}: { content: string, activityId?: number},
public async createPost(
{content, activityId, type}: { content: string, activityId?: number, type: dataAccess.PostType},
request: any): Promise<Post> {
this.ensureLoggedIn(request);
if (content.length > 2048) {
throw new GraphQLError("Content too long.");
}
const post = await dataaccess.createPost(content, request.session.userId, activityId);
const post = await dataAccess.createPost(content, request.session.userId, activityId, type);
globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post);
return post;
}
@ -171,7 +173,7 @@ export class MutationResolver extends BaseResolver {
});
const isAdmin = (await User.findOne({where: {id: request.session.userId}})).isAdmin;
if (post.rAuthor.id === request.session.userId || isAdmin) {
return await dataaccess.deletePost(post.id);
return await dataAccess.deletePost(post.id);
} else {
throw new GraphQLError("User is not author of the post.");
}
@ -188,7 +190,7 @@ export class MutationResolver extends BaseResolver {
if (members) {
chatMembers.push(...members);
}
return await dataaccess.createChat(...chatMembers);
return await dataAccess.createChat(...chatMembers);
}
/**
@ -200,7 +202,7 @@ export class MutationResolver extends BaseResolver {
public async sendMessage({chatId, content}: { chatId: number, content: string }, request: any):
Promise<ChatMessage> {
this.ensureLoggedIn(request);
const message = await dataaccess.sendChatMessage(request.session.userId, chatId, content);
const message = await dataAccess.sendChatMessage(request.session.userId, chatId, content);
globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message);
return message;
}
@ -211,10 +213,10 @@ export class MutationResolver extends BaseResolver {
* @param type
* @param request
*/
public async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }, request: any):
public async sendRequest({receiver, type}: { receiver: number, type: dataAccess.RequestType }, request: any):
Promise<Request> {
this.ensureLoggedIn(request);
return dataaccess.createRequest(request.session.userId, receiver, type);
return dataAccess.createRequest(request.session.userId, receiver, type);
}
/**
@ -223,7 +225,7 @@ export class MutationResolver extends BaseResolver {
* @param type
* @param request
*/
public async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }, request: any) {
public async denyRequest({sender, type}: { sender: number, type: dataAccess.RequestType }, request: any) {
this.ensureLoggedIn(request);
const user = await User.findByPk(request.session.userId);
await user.acceptRequest(sender, type);
@ -236,7 +238,7 @@ export class MutationResolver extends BaseResolver {
* @param type
* @param request
*/
public async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }, request: any) {
public async acceptRequest({sender, type}: { sender: number, type: dataAccess.RequestType }, request: any) {
this.ensureLoggedIn(request);
const user = await User.findByPk(request.session.userId);
await user.acceptRequest(sender, type);
@ -262,7 +264,7 @@ export class MutationResolver extends BaseResolver {
*/
public async createGroup({name, members}: { name: string, members: number[] }, request: any): Promise<Group> {
this.ensureLoggedIn(request);
return await dataaccess.createGroup(name, request.session.userId, members);
return await dataAccess.createGroup(name, request.session.userId, members);
}
/**
@ -291,8 +293,8 @@ export class MutationResolver extends BaseResolver {
*/
public async joinGroup({groupId}: { groupId: number }, request: any): Promise<Group> {
this.ensureLoggedIn(request);
return dataaccess.changeGroupMembership(groupId, request.session.userId,
dataaccess.MembershipChangeAction.ADD);
return dataAccess.changeGroupMembership(groupId, request.session.userId,
dataAccess.MembershipChangeAction.ADD);
}
/**
@ -302,8 +304,8 @@ export class MutationResolver extends BaseResolver {
*/
public async leaveGroup({groupId}: { groupId: number }, request: any): Promise<Group> {
this.ensureLoggedIn(request);
return dataaccess.changeGroupMembership(groupId, request.session.userId,
dataaccess.MembershipChangeAction.REMOVE);
return dataAccess.changeGroupMembership(groupId, request.session.userId,
dataAccess.MembershipChangeAction.REMOVE);
}
/**
@ -319,8 +321,8 @@ export class MutationResolver extends BaseResolver {
if (group && !(await group.$has("rAdmins", user)) && (await group.creator()) !== user.id) {
throw new NotAGroupAdminError(groupId);
}
return dataaccess.changeGroupMembership(groupId, userId,
dataaccess.MembershipChangeAction.OP);
return dataAccess.changeGroupMembership(groupId, userId,
dataAccess.MembershipChangeAction.OP);
}
/**
@ -341,8 +343,8 @@ export class MutationResolver extends BaseResolver {
throw new GraphQLError(
"You are not allowed to remove a creator as an admin.");
}
return await dataaccess.changeGroupMembership(groupId, userId,
dataaccess.MembershipChangeAction.DEOP);
return await dataAccess.changeGroupMembership(groupId, userId,
dataAccess.MembershipChangeAction.DEOP);
}
/**
@ -361,7 +363,7 @@ export class MutationResolver extends BaseResolver {
if (!(await group.$has("rAdmins", user))) {
throw new NotAGroupAdminError(groupId);
}
const blacklisted = await dataaccess.checkBlacklisted(name);
const blacklisted = await dataAccess.checkBlacklisted(name);
if (blacklisted.length > 0) {
throw new BlacklistedError(blacklisted.map((p) => p.phrase), "event name");
}

@ -85,7 +85,7 @@ type Mutation {
sendMessage(chatId: ID!, content: String!): ChatMessage
"create a post that can belong to an activity"
createPost(content: String!, activityId: ID): Post!
createPost(content: String!, activityId: ID, type: PostType = TEXT): Post!
"delete the post for a given post id"
deletePost(postId: ID!): Boolean!
@ -317,6 +317,9 @@ type Post {
"the text of the post"
content: String
"If the post is publically visible"
visible: Boolean!
"the content of the post rendered by markdown-it"
htmlContent: String
@ -543,7 +546,16 @@ enum RequestType {
EVENTINVITE
}
"the type of sorting for getPosts"
enum SortType {
TOP
NEW
}
"""
The type of the post. If the post was created with the type MEDIA,
It stays invisible until a media file has been uploaded for the post
"""
enum PostType {
MEDIA
TEXT
}

Loading…
Cancel
Save