Skip to content

Commit a330cd5

Browse files
authored
Merge pull request #9317 from pedamoci/main
#46 - javascript
2 parents 40017a6 + f4740a2 commit a330cd5

3 files changed

Lines changed: 735 additions & 0 deletions

File tree

Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
import { randomBytes } from 'node:crypto'
2+
import readline from 'readline'
3+
4+
const PREVIOUS_USERS_BY_ID = {
5+
"a1b2c3d4": {
6+
username: "carlos",
7+
posts: ["p1k2l3m4", "p0j1k2l3", "p9i0j1k2", "p8h9i0j1", "p7g8h9i0", "p6f7g8h9", "p5e6f7g8", "p4d5e6f7", "p3c4d5e6", "p2b3c4d5", "p1a2b3c4"],
8+
followings: ["e5f6g7h8", "i9j0k1l2", "m3n4o5p6", "f8g9h0i1"],
9+
likedPosts: ["p22a2b3c", "p33a2b3c", "p44a2b3c", "p55k2l3m", "p56l3m4n"]
10+
},
11+
"e5f6g7h8": {
12+
username: "ana",
13+
posts: ["p23b3c4d", "p22a2b3c"],
14+
followings: ["a1b2c3d4", "m3n4o5p6", "f8g9h0i1"],
15+
likedPosts: ["p1a2b3c4", "p9i0j1k2", "p46c4d5e", "p52i0j1k"]
16+
},
17+
"i9j0k1l2": {
18+
username: "luis",
19+
posts: ["p34b3c4d", "p33a2b3c"],
20+
followings: ["a1b2c3d4", "e5f6g7h8", "f8g9h0i1"],
21+
likedPosts: ["p2b3c4d5", "p23b3c4d", "p55k2l3m", "p1k2l3m4"]
22+
},
23+
"m3n4o5p6": {
24+
username: "sofia",
25+
posts: ["p54k2l3m", "p53j1k2l", "p52i0j1k", "p51h9i0j", "p50g8h9i", "p49f7g8h", "p48e6f7g", "p47d5e6f", "p46c4d5e", "p45b3c4d", "p44a2b3c"],
26+
followings: ["a1b2c3d4", "e5f6g7h8", "i9j0k1l2", "f8g9h0i1"],
27+
likedPosts: ["p1a2b3c4", "p4d5e6f7", "p22a2b3c", "p34b3c4d", "p56l3m4n"]
28+
},
29+
"f8g9h0i1": {
30+
username: "diego",
31+
posts: ["p56l3m4n", "p55k2l3m"],
32+
followings: ["a1b2c3d4", "m3n4o5p6", "i9j0k1l2"],
33+
likedPosts: ["p9i0j1k2", "p44a2b3c", "p52i0j1k", "p1k2l3m4", "p33a2b3c"]
34+
},
35+
}
36+
37+
const PREVIOUS_USERS_BY_USERNAME = {
38+
"carlos": "a1b2c3d4",
39+
"ana": "e5f6g7h8",
40+
"luis": "i9j0k1l2",
41+
"sofia": "m3n4o5p6",
42+
"diego": "f8g9h0i1"
43+
}
44+
45+
const PREVIOUS_POSTS = {
46+
"p1k2l3m4": { text: "¡Viernes al fin!", date: "2022-10-21T17:00:00Z", userId: "a1b2c3d4", likes: 2 },
47+
"p0j1k2l3": { text: "Probando un nuevo restaurante coreano.", date: "2022-10-21T13:00:00Z", userId: "a1b2c3d4", likes: 0 },
48+
"p9i0j1k2": { text: "Nueva configuración de escritorio lista.", date: "2022-10-20T11:10:00Z", userId: "a1b2c3d4", likes: 2 },
49+
"p8h9i0j1": { text: "Extrañando las vacaciones.", date: "2022-10-19T14:20:00Z", userId: "a1b2c3d4", likes: 0 },
50+
"p7g8h9i0": { text: "Cocinando pasta para la cena.", date: "2022-10-18T20:45:00Z", userId: "a1b2c3d4", likes: 0 },
51+
"p6f7g8h9": { text: "Viendo una película clásica.", date: "2022-10-17T21:30:00Z", userId: "a1b2c3d4", likes: 0 },
52+
"p5e6f7g8": { text: "El clima está increíble hoy.", date: "2022-10-16T12:00:00Z", userId: "a1b2c3d4", likes: 0 },
53+
"p4d5e6f7": { text: "Hoy salí a correr 5km.", date: "2022-10-15T07:00:00Z", userId: "a1b2c3d4", likes: 1 },
54+
"p3c4d5e6": { text: "¿Alguien recomienda un buen libro?", date: "2022-10-14T18:00:00Z", userId: "a1b2c3d4", likes: 0 },
55+
"p2b3c4d5": { text: "Aprendiendo Node.js desde cero.", date: "2022-10-13T10:15:00Z", userId: "a1b2c3d4", likes: 1 },
56+
"p1a2b3c4": { text: "Disfrutando de un café por la mañana.", date: "2022-10-12T08:30:00Z", userId: "a1b2c3d4", likes: 2},
57+
"p23b3c4d": { text: "Mi gata no me deja trabajar.", date: "2024-01-16T10:00:00Z", userId: "e5f6g7h8", likes: 1 },
58+
"p22a2b3c": { text: "El viaje a la montaña fue espectacular.", date: "2024-01-15T09:15:22Z", userId: "e5f6g7h8", likes: 2 },
59+
"p34b3c4d": { text: "Terminando mi primer proyecto con React.", date: "2026-03-21T15:30:00Z", userId: "i9j0k1l2", likes: 1 },
60+
"p33a2b3c": { text: "La inteligencia artificial es fascinante.", date: "2026-03-20T11:05:44Z", userId: "i9j0k1l2", likes: 2 },
61+
"p54k2l3m": { text: "Feliz semana a todos.", date: "2015-05-20T08:00:00Z", userId: "m3n4o5p6", likes: 0 },
62+
"p53j1k2l": { text: "Escuchando jazz.", date: "2015-05-19T20:00:00Z", userId: "m3n4o5p6", likes: 0 },
63+
"p52i0j1k": { text: "Pintando con acuarelas.", date: "2015-05-18T17:45:00Z", userId: "m3n4o5p6", likes: 2 },
64+
"p51h9i0j": { text: "Nuevo lienzo en blanco.", date: "2015-05-17T11:30:00Z", userId: "m3n4o5p6", likes: 0 },
65+
"p50g8h9i": { text: "Paz mental.", date: "2015-05-16T14:00:00Z", userId: "m3n4o5p6", likes: 0 },
66+
"p49f7g8h": { text: "Mi jardín está floreciendo.", date: "2015-05-15T09:00:00Z", userId: "m3n4o5p6", likes: 0 },
67+
"p48e6f7g": { text: "Mirando las estrellas.", date: "2015-05-14T22:00:00Z", userId: "m3n4o5p6", likes: 0 },
68+
"p47d5e6f": { text: "El té de jazmín es lo mejor.", date: "2015-05-13T10:10:00Z", userId: "m3n4o5p6", likes: 0 },
69+
"p46c4d5e": { text: "Leyendo bajo la lluvia.", date: "2015-05-12T16:20:00Z", userId: "m3n4o5p6", likes: 1 },
70+
"p45b3c4d": { text: "Caminata nocturna.", date: "2015-05-11T23:00:00Z", userId: "m3n4o5p6", likes: 0 },
71+
"p44a2b3c": { text: "Un pequeño poema para el alma.", date: "2015-05-10T22:45:10Z", userId: "m3n4o5p6", likes: 2 },
72+
"p56l3m4n": { text: "La disciplina vence al talento.", date: "2025-02-28T09:30:00Z", userId: "f8g9h0i1", likes: 2 },
73+
"p55k2l3m": { text: "Entrenando duro para el maratón.", date: "2025-02-27T08:00:15Z", userId: "f8g9h0i1", likes: 2 }
74+
}
75+
76+
const rl = readline.createInterface({
77+
input: process.stdin,
78+
output: process.stdout
79+
})
80+
81+
const createErrorMsg = msg => {
82+
return `\n****************************************************************\n` +
83+
`Error: ${msg}` +
84+
`\n****************************************************************\n`
85+
}
86+
const createSuccessMsg = msg => {
87+
return `\n----------------------------------------------------------------\n` +
88+
`Success: ${msg}` +
89+
`\n----------------------------------------------------------------\n`
90+
}
91+
const createMenuMsg = () => {
92+
return "--------------------- ACTIONS ----------------------\n" +
93+
"| 1. Register user 2. Follow user |\n" +
94+
"| 3. Unfollow user 4. Create post |\n" +
95+
"| 5. Delete post 6. Like post |\n" +
96+
"| 7. Unlike post 8. View user feed |\n" +
97+
"| 9. View followings feed 10. Exit |\n" +
98+
"----------------------------------------------------"
99+
}
100+
101+
const createPostMsg = info => {
102+
return `~~~~~~~~~~~~~~~~~~~~~ POST ~~~~~~~~~~~~~~~~~~~~~~\n` +
103+
`${info.text}\n` +
104+
`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n` +
105+
`Date: ${new Date(info.date).toLocaleString()} Likes:${info.likes}\n` +
106+
`User: ${info.username} Post ID: ${info.postId}\n` +
107+
`User ID: ${info.userId}\n`
108+
}
109+
110+
const ask = q => new Promise(r => rl.question(q,r))
111+
112+
class Renderer {
113+
static error(msg) {
114+
console.error(createErrorMsg(msg))
115+
}
116+
117+
static success(msg) {
118+
console.log(createSuccessMsg(msg))
119+
}
120+
121+
static menu() {
122+
console.log(createMenuMsg())
123+
}
124+
125+
static post(info) {
126+
console.log(createPostMsg(info))
127+
}
128+
}
129+
130+
class Validator {
131+
static isValidUsername(username) {
132+
if (!username) throw new Error("The username cannot be empty")
133+
return username
134+
}
135+
136+
static isValidText(text) {
137+
if (!text || text.length > 200) throw new Error("The text is invalid")
138+
return text
139+
}
140+
}
141+
142+
class IdHelper {
143+
static createId() { return randomBytes(4).toString("hex") }
144+
145+
static getValidId(usersIds, postsId) {
146+
let i = 0
147+
148+
while (i < 10) {
149+
const id = IdHelper.createId()
150+
if (!usersIds[id] && !postsId[id]) return id
151+
i++
152+
}
153+
154+
throw new Error("The ID could not be created correctly. Please try again later.")
155+
}
156+
}
157+
158+
class FeedHelper {
159+
static getPostsId(usersId, users) {
160+
const postsId = usersId.flatMap(userId => users[userId].posts)
161+
return postsId
162+
}
163+
164+
static getOrderPosts(postsId, posts) {
165+
const orderPostsId = [...postsId].sort((postIdOne, postIdTwo) => {
166+
if (posts[postIdOne].date > posts[postIdTwo].date) return -1
167+
return 1
168+
}).slice(0, 10)
169+
return orderPostsId
170+
}
171+
}
172+
173+
class InputHelper {
174+
static async getExistingUsername(users) {
175+
const username = Validator.isValidUsername((await ask("Enter the username: ")).trim())
176+
if (!users[username]) throw new Error("The username does not exists")
177+
return username
178+
}
179+
180+
static async getExistingPostId(postsId) {
181+
const id = (await ask("Enter the post id: ")).trim()
182+
if (!postsId[id]) throw new Error("There is no post with this ID")
183+
return id
184+
}
185+
}
186+
187+
class User {
188+
constructor(username) {
189+
this.username = username
190+
this.posts = []
191+
this.followings = []
192+
this.likedPosts = []
193+
}
194+
}
195+
196+
class Post {
197+
constructor(userId, text) {
198+
this.text = text
199+
this.userId = userId
200+
this.date = new Date()
201+
this.likes = 0
202+
}
203+
}
204+
205+
class UserService {
206+
constructor(usersById, usersByUsername) {
207+
this.usersById = usersById
208+
this.usersByUsername = usersByUsername
209+
}
210+
211+
registerUser(userId, username) {
212+
this.usersById[userId] = new User(username)
213+
this.usersByUsername[username] = userId
214+
return Renderer.success("The user has successfully registered")
215+
}
216+
217+
followUser(sourceUserId, targetUserId) {
218+
this.usersById[sourceUserId].followings.push(targetUserId)
219+
return Renderer.success(`${this.usersById[sourceUserId].username} started following ${this.usersById[targetUserId].username}`)
220+
}
221+
222+
unfollowUser(sourceUserId, targetUserId) {
223+
this.usersById[sourceUserId].followings = this.usersById[sourceUserId].followings.filter(fId => fId !== targetUserId)
224+
return Renderer.success(`${this.usersById[sourceUserId].username} has unfollowing ${this.usersById[targetUserId].username}`)
225+
}
226+
}
227+
228+
class PostService {
229+
constructor(usersById, posts) {
230+
this.usersById = usersById
231+
this.posts = posts
232+
}
233+
234+
createPost(userId, postId, text) {
235+
this.posts[postId] = new Post(userId, text)
236+
this.usersById[userId].posts.push(postId)
237+
return Renderer.success("The post has successfully crated")
238+
}
239+
240+
deletePost(userId, postId) {
241+
for (const userInfo of Object.values(this.usersById)) {
242+
if (userInfo.likedPosts.includes(postId)) userInfo.likedPosts = userInfo.likedPosts.filter(pId => pId !== postId)
243+
}
244+
delete this.posts[postId]
245+
this.usersById[userId].posts = this.usersById[userId].posts.filter(pId => pId !== postId)
246+
return Renderer.success("The post has successfully deleted")
247+
}
248+
249+
likePost(userId, postId) {
250+
this.posts[postId].likes++
251+
this.usersById[userId].likedPosts.push(postId)
252+
return Renderer.success(`${this.usersById[userId].username} has liked the post`)
253+
}
254+
255+
unlikePost(userId, postId) {
256+
this.posts[postId].likes--
257+
this.usersById[userId].likedPosts = this.usersById[userId].likedPosts.filter(pId => pId !== postId)
258+
return Renderer.success(`${this.usersById[userId].username} has unliked the post`)
259+
}
260+
}
261+
262+
class FeedService {
263+
constructor(usersById, posts) {
264+
this.usersById = usersById
265+
this.posts = posts
266+
}
267+
268+
viewFeed(userId) {
269+
const orderPostsId = FeedHelper.getOrderPosts(this.usersById[userId].posts, posts)
270+
for (const postId of orderPostsId) {
271+
Renderer.post({
272+
text: posts[postId].text,
273+
date: posts[postId].date,
274+
likes: posts[postId].likes,
275+
userId,
276+
username: this.usersById[userId].username,
277+
postId
278+
})
279+
}
280+
}
281+
282+
viewFollowingsFeed(userId) {
283+
const orderPostsId = FeedHelper.getOrderPosts(FeedHelper.getPostsId(this.usersById[userId].followings, this.usersById), this.posts)
284+
for (const postId of orderPostsId) {
285+
Renderer.post({
286+
text: this.posts[postId].text,
287+
date: this.posts[postId].date,
288+
likes: this.posts[postId].likes,
289+
userId: this.posts[postId].userId,
290+
username: this.usersById[posts[postId].userId].username,
291+
postId
292+
})
293+
}
294+
}
295+
}
296+
297+
class Program {
298+
constructor(userService, postService, feedService) {
299+
this.usersById = PREVIOUS_USERS_BY_ID
300+
this.usersByUsername = PREVIOUS_USERS_BY_USERNAME
301+
this.posts = PREVIOUS_POSTS
302+
this.userService = new userService(this.usersById, this.usersByUsername)
303+
this.postService = new postService(this.usersById, this.posts)
304+
this.feedService = new feedService(this.usersById, this.posts)
305+
}
306+
307+
async start() {
308+
let opt
309+
do {
310+
Renderer.menu()
311+
opt = (await ask("Enter the number of the action: ")).trim()
312+
try {
313+
await this.setOption(opt)
314+
} catch (e) {
315+
Renderer.error(e.message)
316+
}
317+
} while (opt !== "10")
318+
rl.close()
319+
}
320+
321+
async setOption(opt) {
322+
switch (opt) {
323+
case "1":{
324+
const username = await Validator.isValidUsername((await ask("Enter the username: ")).trim())
325+
if (this.usersByUsername[username]) throw new Error("The username already exists")
326+
const id = IdHelper.getValidId(this.usersById, this.posts)
327+
this.userService.registerUser(id, username)
328+
break}
329+
case "2":{
330+
const sourceUserId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
331+
const targetUserId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
332+
if (sourceUserId === targetUserId) throw new Error("You can't follow yourself")
333+
if (this.usersById[sourceUserId].followings.includes(targetUserId)) throw new Error("You already follow this user")
334+
this.userService.followUser(sourceUserId, targetUserId)
335+
break}
336+
case "3":{
337+
const sourceUserId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
338+
const targetUserId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
339+
if (!this.usersById[sourceUserId].followings.includes(targetUserId)) throw new Error("You don't follow this user")
340+
this.userService.unfollowUser(sourceUserId, targetUserId)
341+
break}
342+
case "4":{
343+
const userId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
344+
const text = await Validator.isValidText((await ask("Enter the text: ")).trim())
345+
const postId = IdHelper.getValidId(this.usersById, this.posts)
346+
this.postService.createPost(userId, postId, text)
347+
break}
348+
case "5":{
349+
const userId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
350+
const postId = await InputHelper.getExistingPostId(this.posts)
351+
if (!this.usersById[userId].posts.includes(postId)) throw new Error("The user has no posts with that ID")
352+
this.postService.deletePost(userId, postId)
353+
break}
354+
case "6":{
355+
const userId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
356+
const postId = await InputHelper.getExistingPostId(this.posts)
357+
if (this.usersById[userId].posts.includes(postId)) throw new Error("You can't like your own post")
358+
if (this.usersById[userId].likedPosts.includes(postId)) throw new Error("You've already liked that post")
359+
this.postService.likePost(userId, postId)
360+
break}
361+
case "7":{
362+
const userId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
363+
const postId = await InputHelper.getExistingPostId(this.posts)
364+
if (!this.usersById[userId].likedPosts.includes(postId)) throw new Error("You haven't liked that post")
365+
this.postService.unlikePost(userId, postId)
366+
break}
367+
case "8":{
368+
const userId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
369+
if (this.usersById[userId].posts.length === 0) throw new Error("The user has no posts")
370+
this.feedService.viewFeed(userId)
371+
break}
372+
case "9":{
373+
const userId = this.usersByUsername[await InputHelper.getExistingUsername(this.usersByUsername)]
374+
if (this.usersById[userId].followings.length === 0) throw new Error("The user doesn't follow anyone")
375+
this.feedService.viewFollowingsFeed(userId)
376+
break}
377+
case "0":{
378+
console.log(this.usersById)
379+
console.log(this.posts)
380+
break}
381+
default:break;
382+
}
383+
}
384+
}
385+
386+
const program = new Program(UserService, PostService, FeedService)
387+
program.start()

0 commit comments

Comments
 (0)