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
3 changes: 3 additions & 0 deletions backend/data/blooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class Bloom:


def add_bloom(*, sender: User, content: str) -> Bloom:
# // 280 char limit
if len(content) > 280:
return {"error": "Too long!"}, 400
hashtags = [word[1:] for word in content.split(" ") if word.startswith("#")]

now = datetime.datetime.now(tz=datetime.UTC)
Expand Down
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
70 changes: 38 additions & 32 deletions front-end/components/bloom-form.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {apiService} from "../index.mjs";
import { apiService } from "../index.mjs";

/**
* Create a bloom form component
Expand All @@ -7,52 +7,58 @@ import {apiService} from "../index.mjs";
* @returns {DocumentFragment} - The bloom form fragment
*/
function createBloomForm(template, isLoggedIn) {
if (!isLoggedIn) return;
const bloomFormElement = document
.getElementById(template)
.content.cloneNode(true);
if (!isLoggedIn) return;
const bloomFormElement = document
.getElementById(template)
.content.cloneNode(true);

return bloomFormElement;
return bloomFormElement;
}

/**
* Handle bloom form submission
* @param {Event} event - The form submission event
*/
async function handleBloomSubmit(event) {
event.preventDefault();
const form = event.target;
const submitButton = form.querySelector("[data-submit]");
const originalText = submitButton.textContent;
const textarea = form.querySelector("textarea");
const content = textarea.value.trim();
event.preventDefault();
const form = event.target;
const submitButton = form.querySelector("[data-submit]");
const originalText = submitButton.textContent;
const textarea = form.querySelector("textarea");
const content = textarea.value.trim();

try {
// Make form inert while we call the back end
form.inert = true;
submitButton.textContent = "Posting...";
await apiService.postBloom(content);
textarea.value = "";
} catch (error) {
throw error;
} finally {
// Restore form
submitButton.textContent = originalText;
form.inert = false;
}
//add char limit
if (content.length > 280) {
alert("Too long! Maximum 280 characters.");
return;
}

try {
// Make form inert while we call the back end
form.inert = true;
submitButton.textContent = "Posting...";
await apiService.postBloom(content);
textarea.value = "";
} catch (error) {
throw error;
} finally {
// Restore form
submitButton.textContent = originalText;
form.inert = false;
}
}

/**
* Handle textarea input for bloom form
* @param {Event} event - The input event from textarea drives the character counter
*/
function handleTyping(event) {
const textarea = event.target;
const counter = textarea
.closest("[data-form]")
?.querySelector("[data-counter]");
const maxLength = parseInt(textarea.getAttribute("maxlength"), 10);
counter.textContent = `${textarea.value.length} / ${maxLength}`;
const textarea = event.target;
const counter = textarea
.closest("[data-form]")
?.querySelector("[data-counter]");
const maxLength = parseInt(textarea.getAttribute("maxlength"), 10);
counter.textContent = `${textarea.value.length} / ${maxLength}`;
}

export {createBloomForm, handleBloomSubmit, handleTyping};
export { createBloomForm, handleBloomSubmit, handleTyping };
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 };