Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 };