All files post.ts

100% Statements 132/132
100% Branches 41/41
100% Functions 23/23
100% Lines 109/109

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457          20x                                                                                                                                                                                                                                   20x                                     20x                     17x 17x 17x   9x 9x 9x   9x                     9x                                 18x   18x 18x   9x                 20x 6x 6x 6x   6x                 20x 4x 4x 4x   4x               20x 8x 8x 8x   16x 16x 16x   8x 8x 16x   8x 16x                 20x       1x 2x 2x 1x 2x 2x   1x     1x 2x                 20x 2x 2x 2x   2x 2x   2x                 20x 2x 2x 2x   2x 2x   2x                 20x 2x             2x 2x   2x               20x 15x 15x 15x   15x   30x 4x     15x 15x 30x   15x   29x 1x 1x 2x                   20x 3x 3x 3x   3x                 20x 1x 1x 2x                 20x 2x                   20x       6x                 20x 4x 4x 4x   4x                 20x   7x   4x 4x 8x               20x 2x                               2x   2x   20x  
/**
 * Services for the post table.
 * @packageDocumentation
 */
 
import { BaseService, getTime, newUniqueID } from "./util";
import { Image } from "./image";
import { Rating, RatingParams } from "./rating";
import { User } from "./user";
 
/**
 * Post architecture.
 */
export interface Post {
  id: string;
  userID: string;
  content: string;
  location: string;
  city: string;
  country: string;
  locationTypeID: number;
  programID: number;
  ratingID: string;
  threeWords: string;
  currentUserStatusID: number;
  address: string | null;
  phone: string | null;
  website: string | null;
  approved: boolean;
  createTime: number;
  editTime: number | null;
}
 
/**
 * Post with only ID architecture.
 */
interface PostID {
  id: string;
}
 
/**
 * Post with only user ID architecture.
 */
interface PostUserID {
  userID: string;
}
 
/**
 * Post with only rating ID architecture.
 */
interface PostRatingID {
  ratingID: string;
}
 
/**
 * Post with only content architecture.
 */
interface PostContent {
  content: string;
}
 
/**
 * Post with only approved architecture.
 */
interface PostApproved {
  approved: boolean;
}
 
/**
 * Post with only ID and rating ID architecture.
 */
interface PostIDRatingID {
  id: string;
  ratingID: string;
}
 
/**
 * Unapproved post architecture.
 */
export interface UnapprovedPost {
  postID: string;
  firstname: string;
  lastname: string;
  content: string;
  location: string;
  city: string;
  country: string;
  locationType: string;
  program: string;
  threeWords: string;
  createTime: number;
}
 
/**
 * User post architecture.
 */
export interface UserPost extends Post {
  program: string;
}
 
/**
 * Post details architecture.
 */
interface PostDetails {
  content?: string;
  location?: string;
  city?: string;
  country?: string;
  locationTypeID?: number;
  programID?: number;
  threeWords?: string;
  address?: string;
  phone?: string;
  website?: string;
}
 
/**
 * Post services.
 */
export class PostService extends BaseService {
  /**
   * Create a post.
   *
   * @param userID The ID of the user making the post.
   * @param content The text content of the post.
   * @param imageData The binary data of the images associated with the post.
   * @param location The post's location.
   * @param city The location's city.
   * @param country The location's country.
   * @param locationTypeID The type ID of location.
   * @param programID The ID of the program the user is in.
   * @param rating The user's rating of the location.
   * @param threeWords Three words to describe the location.
   * @param address The location's address.
   * @param phone The location's phone number.
   * @param website The location's website.
   * @returns The new post's ID.
   */
  public async createPost(
    userID: string,
    content: string,
    imageData: Buffer[],
    location: string,
    city: string,
    country: string,
    locationTypeID: number,
    programID: number,
    rating: RatingParams,
    threeWords: string,
    address: string = null,
    phone: string = null,
    website: string = null
  ): Promise<string> {
    const postID = await newUniqueID(this.dbm, "Post");
    const ratingID = await this.dbm.ratingService.createRating(rating);
    const user = await this.dbm.userService.getUser(userID);
 
    const sql = `
      INSERT INTO Post (
        id, userID, content, location, city, country, locationTypeID,
        programID, ratingID, threeWords, currentUserStatusID, address, phone,
        website, createTime
      ) VALUES (
        ?, ?, ?, ?, ?, ?, ?,
        ?, ?, ?, ?, ?, ?,
        ?, ?
      );
    `;
    const params = [
      postID,
      userID,
      content,
      location,
      city,
      country,
      locationTypeID,
      programID,
      ratingID,
      threeWords,
      user.statusID,
      address,
      phone,
      website,
      getTime(),
    ];
    await this.dbm.execute(sql, params);
 
    await this.dbm.postImageService.createPostImages(postID, imageData);
    await this.dbm.userService.updateLastPostTime(userID);
 
    return postID;
  }
 
  /**
   * Check if a post exists.
   *
   * @param postID A post's ID.
   * @returns Whether or not the post exists.
   */
  public async postExists(postID: string): Promise<boolean> {
    const sql = `SELECT id FROM Post WHERE id = ?;`;
    const params = [postID];
    const rows: PostID[] = await this.dbm.execute(sql, params);
 
    return rows.length > 0;
  }
 
  /**
   * Get a post.
   *
   * @param postID A post's ID.
   * @returns The post.
   */
  public async getPost(postID: string): Promise<Post> {
    const sql = `SELECT * FROM Post WHERE id = ?;`;
    const params = [postID];
    const rows: Post[] = await this.dbm.execute(sql, params);
 
    return rows[0];
  }
 
  /**
   * Delete a post.
   *
   * @param postID A post's ID.
   */
  public async deletePost(postID: string): Promise<void> {
    let sql = `SELECT ratingID FROM Post WHERE id = ?;`;
    let params = [postID];
    let rows: PostRatingID[] = await this.dbm.execute(sql, params);
 
    await this.dbm.postImageService.deletePostImages(postID);
    await this.dbm.adminFavoritesService.unfavorite(postID);
    await this.dbm.postVoteService.deletePostVotes(postID);
 
    sql = `DELETE FROM Post WHERE id = ?;`;
    params = [postID];
    await this.dbm.execute(sql, params);
 
    const ratingID = rows[0]?.ratingID;
    await this.dbm.ratingService.deleteRating(ratingID);
  }
 
  /**
   * Edit a post, given the parts of the post that have been edited.
   *
   * @param postID A post's ID.
   * @param postDetails The post details.
   */
  public async editPost(
    postID: string,
    postDetails: PostDetails
  ): Promise<void> {
    const newFields = Object.keys(postDetails)
      .filter((field) => field !== undefined && field !== null)
      .map((field) => `${field} = ?`);
    const newValues = Object.keys(postDetails)
      .filter((field) => field !== undefined && field !== null)
      .map((field) => postDetails[field]);
 
    const sql = `UPDATE Post SET ${newFields.join(
      ", "
    )}, editTime = ? WHERE id = ?;`;
    const params = [...newValues, getTime(), postID];
    await this.dbm.execute(sql, params);
  }
 
  /**
   * Get the user who made the post.
   *
   * @param postID A post's ID.
   * @returns The user who made the post.
   */
  public async getPostUser(postID: string): Promise<User> {
    const sql = `SELECT userID FROM Post WHERE id = ?;`;
    const params = [postID];
    const rows: PostUserID[] = await this.dbm.execute(sql, params);
 
    const userID = rows[0]?.userID;
    const user = await this.dbm.userService.getUser(userID);
 
    return user;
  }
 
  /**
   * Get a post's rating.
   *
   * @param postID A post's ID.
   * @returns The post's rating.
   */
  public async getPostRating(postID: string): Promise<Rating> {
    const sql = `SELECT ratingID FROM Post WHERE id = ?;`;
    const params = [postID];
    const rows: PostRatingID[] = await this.dbm.execute(sql, params);
 
    const ratingID = rows[0]?.ratingID;
    const rating = await this.dbm.ratingService.getRating(ratingID);
 
    return rating;
  }
 
  /**
   * Get all of a user's posts.
   *
   * @param userID A user's ID.
   * @returns A list of all posts made by the user.
   */
  public async getUserPosts(userID: string): Promise<UserPost[]> {
    const sql = `
      SELECT Post.*, Program.name AS program
        FROM Post
        JOIN Program ON Post.programID = Program.id
      WHERE Post.userID = ?
      ORDER BY Post.createTime;
    `;
    const params = [userID];
    const rows: UserPost[] = await this.dbm.execute(sql, params);
 
    return rows;
  }
 
  /**
   * Delete all of a user's posts.
   *
   * @param userID A user's ID.
   */
  public async deleteUserPosts(userID: string): Promise<void> {
    let sql = `SELECT id, ratingID FROM Post WHERE userID = ?;`;
    let params = [userID];
    const rows: PostIDRatingID[] = await this.dbm.execute(sql, params);
 
    const postIDs = rows.map((post) => post.id);
 
    for (const postID of postIDs) {
      await this.dbm.postImageService.deletePostImages(postID);
    }
 
    sql = `DELETE FROM Post WHERE userID = ?;`;
    params = [userID];
    await this.dbm.execute(sql, params);
 
    const ratingIDs = rows.map((post) => `'${post.ratingID}'`);
 
    if (ratingIDs.length > 0) {
      sql = `DELETE FROM Rating WHERE id IN (${ratingIDs.join(", ")});`;
      params = [];
      await this.dbm.execute(sql, params);
    }
  }
 
  /**
   * Get a post's text content.
   *
   * @param postID A post's ID.
   * @returns The post's text content.
   */
  public async getPostContent(postID: string): Promise<string> {
    const sql = `SELECT content FROM Post WHERE id = ?;`;
    const params = [postID];
    const rows: PostContent[] = await this.dbm.execute(sql, params);
 
    return rows[0]?.content;
  }
 
  /**
   * Set a post's text content.
   *
   * @param postID A post's ID.
   * @param content The new text content of a post.
   */
  public async setPostContent(postID: string, content: string): Promise<void> {
    const sql = `UPDATE Post SET content = ? WHERE id = ?;`;
    const params = [content, postID];
    await this.dbm.execute(sql, params);
  }
 
  /**
   * Get a post's images.
   *
   * @param postID A post's ID.
   * @returns The images associated with the post.
   */
  public async getPostImages(postID: string): Promise<Image[]> {
    return await this.dbm.postImageService.getPostImages(postID);
  }
 
  /**
   * Set a post's images.
   *
   * @param postID A post's ID.
   * @param imageData The binary data of the new images.
   * @returns The IDs of the new images.
   */
  public async setPostImages(
    postID: string,
    imageData: Buffer[]
  ): Promise<string[]> {
    return await this.dbm.postImageService.createPostImages(postID, imageData);
  }
 
  /**
   * Check if a post has been approved.
   *
   * @param postID A post's ID.
   * @returns Whether or not the post has been approved by an admin.
   */
  public async isApproved(postID: string): Promise<boolean> {
    const sql = `SELECT approved FROM Post WHERE id = ?;`;
    const params = [postID];
    const rows: PostApproved[] = await this.dbm.execute(sql, params);
 
    return !!rows[0]?.approved;
  }
 
  /**
   * Set a post's approved status.
   *
   * @param postID A post's ID.
   * @param approved Approved status.
   */
  public async setApproved(
    postID: string,
    approved: boolean = true
  ): Promise<void> {
    const sql = `UPDATE Post SET approved = ? WHERE id = ?;`;
    const params = [approved, postID];
    await this.dbm.execute(sql, params);
  }
 
  /**
   * Get all unapproved posts.
   *
   * @returns All unapproved posts.
   */
  public async getUnapproved(): Promise<UnapprovedPost[]> {
    const sql = `
      SELECT
        Post.id AS postID, User.firstname AS firstname,
        User.lastname AS lastname, content, location, city, country,
        LocationType.name AS locationType, Program.name AS program,
        threeWords, createTime
      FROM Post
      JOIN User
        ON Post.userID = User.id
      JOIN LocationType
        ON Post.locationTypeID = LocationType.id
      JOIN Program
        ON Post.programID = Program.id
      WHERE Post.approved = FALSE
      ORDER BY createTime;
    `;
    const rows: UnapprovedPost[] = await this.dbm.execute(sql);
 
    return rows;
  }
}