Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions backend/data/users.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from hashlib import scrypt
# from hashlib import scrypt
import hashlib
import random
import string
Expand Down Expand Up @@ -90,8 +90,10 @@ def register_user(username: str, password_plaintext: str) -> User:
raise UserRegistrationError("user already exists")


# def scrypt(password_plaintext: bytes, password_salt: bytes) -> bytes:
# return hashlib.scrypt(password_plaintext, salt=password_salt, n=8, r=8, p=1)
def scrypt(password_plaintext: bytes, password_salt: bytes) -> bytes:
return hashlib.scrypt(password_plaintext, salt=password_salt, n=8, r=8, p=1)
return hashlib.pbkdf2_hmac('sha256', password_plaintext, password_salt, 100000)


SALT_CHARACTERS = string.ascii_uppercase + string.ascii_lowercase + string.digits
Expand Down
116 changes: 59 additions & 57 deletions front-end/components/bloom.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,77 +11,79 @@

*/
const createBloom = (template, bloom) => {
if (!bloom) return;
const bloomFrag = document.getElementById(template).content.cloneNode(true);
const bloomParser = new DOMParser();
if (!bloom) return;
const bloomFrag = document.getElementById(template).content.cloneNode(true);
const bloomParser = new DOMParser();

const bloomArticle = bloomFrag.querySelector("[data-bloom]");
const bloomUsername = bloomFrag.querySelector("[data-username]");
const bloomTime = bloomFrag.querySelector("[data-time]");
const bloomTimeLink = bloomFrag.querySelector("a:has(> [data-time])");
const bloomContent = bloomFrag.querySelector("[data-content]");
const bloomArticle = bloomFrag.querySelector("[data-bloom]");
const bloomUsername = bloomFrag.querySelector("[data-username]");
const bloomTime = bloomFrag.querySelector("[data-time]");
const bloomTimeLink = bloomFrag.querySelector("a:has(> [data-time])");
const bloomContent = bloomFrag.querySelector("[data-content]");

bloomArticle.setAttribute("data-bloom-id", bloom.id);
bloomUsername.setAttribute("href", `/profile/${bloom.sender}`);
bloomUsername.textContent = bloom.sender;
bloomTime.textContent = _formatTimestamp(bloom.sent_timestamp);
bloomTimeLink.setAttribute("href", `/bloom/${bloom.id}`);
bloomContent.replaceChildren(
...bloomParser.parseFromString(_formatHashtags(bloom.content), "text/html")
.body.childNodes
);
bloomArticle.setAttribute("data-bloom-id", bloom.id);
bloomUsername.setAttribute("href", `/profile/${bloom.sender}`);
bloomUsername.textContent = bloom.sender;
bloomTime.textContent = _formatTimestamp(bloom.sent_timestamp);
bloomTimeLink.setAttribute("href", `/bloom/${bloom.id}`);
bloomContent.replaceChildren(
...bloomParser.parseFromString(_formatHashtags(bloom.content), "text/html")
.body.childNodes
);

return bloomFrag;
return bloomFrag;
};

function _formatHashtags(text) {
if (!text) return text;
return text.replace(
/\B#[^#]+/g,
(match) => `<a href="/hashtag/${match.slice(1)}">${match}</a>`
);
if (!text) return text;
return text.replace(
/\B#[^#]+/g, // does not grab #
/\B#[a-zA-Z0-9_]+/g,

(match) => `<a href="/hashtag/${match.slice(1)}">${match}</a>`
);
}

function _formatTimestamp(timestamp) {
if (!timestamp) return "";
if (!timestamp) return "";

try {
const date = new Date(timestamp);
const now = new Date();
const diffSeconds = Math.floor((now - date) / 1000);
try {
const date = new Date(timestamp);
const now = new Date();
const diffSeconds = Math.floor((now - date) / 1000);

// Less than a minute
if (diffSeconds < 60) {
return `${diffSeconds}s`;
}
// Less than a minute
if (diffSeconds < 60) {
return `${diffSeconds}s`;
}

// Less than an hour
const diffMinutes = Math.floor(diffSeconds / 60);
if (diffMinutes < 60) {
return `${diffMinutes}m`;
}
// Less than an hour
const diffMinutes = Math.floor(diffSeconds / 60);
if (diffMinutes < 60) {
return `${diffMinutes}m`;
}

// Less than a day
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) {
return `${diffHours}h`;
}
// Less than a day
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) {
return `${diffHours}h`;
}

// Less than a week
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 7) {
return `${diffDays}d`;
}
// Less than a week
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 7) {
return `${diffDays}d`;
}

// Format as month and day for older dates
return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
}).format(date);
} catch (error) {
console.error("Failed to format timestamp:", error);
return "";
}
// Format as month and day for older dates
return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
}).format(date);
} catch (error) {
console.error("Failed to format timestamp:", error);
return "";
}
}

export {createBloom};
export { createBloom };
113 changes: 58 additions & 55 deletions front-end/views/profile.mjs
Original file line number Diff line number Diff line change
@@ -1,66 +1,69 @@
import {renderEach, renderOne, destroy} from "../lib/render.mjs";
import { renderEach, renderOne, destroy } from "../lib/render.mjs";
import {
apiService,
state,
getLogoutContainer,
getLoginContainer,
getProfileContainer,
getTimelineContainer,
apiService,
state,
getLogoutContainer,
getLoginContainer,
getProfileContainer,
getTimelineContainer,
} from "../index.mjs";
import {createLogin, handleLogin} from "../components/login.mjs";
import {createLogout, handleLogout} from "../components/logout.mjs";
import {createProfile, handleFollow} from "../components/profile.mjs";
import {createBloom} from "../components/bloom.mjs";
import { createLogin, handleLogin } from "../components/login.mjs";
import { createLogout, handleLogout } from "../components/logout.mjs";
import { createProfile, handleFollow } from "../components/profile.mjs";
import { createBloom } from "../components/bloom.mjs";

// Profile view - just this person's blooms and their profile
function profileView(username) {
destroy();
destroy();

const existingProfile = state.profiles.find((p) => p.username === username);
const existingProfile = state.profiles.find((p) => p.username === username);

// Only fetch profile if we don't have it or if it's incomplete
if (!existingProfile || !existingProfile.recent_blooms) {
apiService.getProfile(username);
}
// Only fetch profile if we don't have it or if it's incomplete
if (!existingProfile || !existingProfile.recent_blooms) {
apiService.getProfile(username);
}

renderOne(
state.isLoggedIn,
getLogoutContainer(),
"logout-template",
createLogout
);
document
.querySelector("[data-action='logout']")
?.addEventListener("click", handleLogout);
renderOne(
state.isLoggedIn,
getLoginContainer(),
"login-template",
createLogin
);
document
.querySelector("[data-action='login']")
?.addEventListener("click", handleLogin);
renderOne(
state.isLoggedIn,
getLogoutContainer(),
"logout-template",
createLogout
);
document
.querySelector("[data-action='logout']")
?.addEventListener("click", handleLogout);
renderOne(
state.isLoggedIn,
getLoginContainer(),
"login-template",
createLogin
);
document
// .querySelector("[data-action='login']")
// ?.addEventListener("click", handleLogin);
.querySelector(".login__form")
?.addEventListener("submit", handleLogin);
//submit event gets data from inputs

const profileData = state.profiles.find((p) => p.username === username);
if (profileData) {
renderOne(
{
profileData,
whoToFollow: state.isLoggedIn ? state.whoToFollow : [],
isLoggedIn: state.isLoggedIn,
},
getProfileContainer(),
"profile-template",
createProfile
);
renderEach(
profileData.recent_blooms || [],
getTimelineContainer(),
"bloom-template",
createBloom
);
}
const profileData = state.profiles.find((p) => p.username === username);
if (profileData) {
renderOne(
{
profileData,
whoToFollow: state.isLoggedIn ? state.whoToFollow : [],
isLoggedIn: state.isLoggedIn,
},
getProfileContainer(),
"profile-template",
createProfile
);
renderEach(
profileData.recent_blooms || [],
getTimelineContainer(),
"bloom-template",
createBloom
);
}
}

export {profileView};
export { profileView };