Initial version

This commit is contained in:
2025-06-15 18:40:30 -06:00
parent 78eb6e64df
commit 2f08b94c89
7 changed files with 1682 additions and 0 deletions

145
todo-service.js Normal file
View File

@ -0,0 +1,145 @@
import { SignalClient } from "./signal-crypto.js";
import { v4 as uuidv4 } from "uuid";
export class EncryptedTodoService {
constructor() {
this.users = new Map(); // userId -> SignalClient
this.todos = new Map(); // todoId -> encrypted todo
this.userTodos = new Map(); // userId -> Set of todoIds
this.sharedSessions = new Map(); // userId+recipientId -> shared session
}
async createUser(userId) {
if (this.users.has(userId)) {
console.log(`User ${userId} already exists. Logging in.`);
return { userId };
}
const client = await SignalClient.create(userId);
this.users.set(userId, client);
this.userTodos.set(userId, new Set());
return { userId };
}
getUser(userId) {
const user = this.users.get(userId);
if (!user) {
throw new Error("User not found. Please create the user first.");
}
return user;
}
async addTodo(userId, todoText) {
const client = this.getUser(userId);
const todoId = uuidv4();
// Self-encrypt the todo
const encryptedTodo = await client.encrypt(todoText);
this.todos.set(todoId, {
id: todoId,
userId,
encrypted: encryptedTodo,
createdAt: new Date().toISOString(),
originalText: todoText, // Store original for sharing (in a real app, this would be encrypted)
});
this.userTodos.get(userId).add(todoId);
console.log(`Todo added for user ${userId}: ${todoId}`);
return todoId;
}
async getTodos(userId) {
const client = this.getUser(userId);
const todoIds = this.userTodos.get(userId) || new Set();
const todos = [];
for (const todoId of todoIds) {
const encryptedTodo = this.todos.get(todoId);
if (encryptedTodo) {
try {
const decryptedText = await client.decrypt(encryptedTodo.encrypted);
todos.push({
id: todoId,
text: decryptedText,
createdAt: encryptedTodo.createdAt,
sharedBy: encryptedTodo.sharedBy,
sharedWith: encryptedTodo.sharedWith || [],
});
} catch (error) {
console.error(`Failed to decrypt todo ${todoId}:`, error);
}
}
}
return todos.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
}
async deleteTodo(userId, todoId) {
const userTodos = this.userTodos.get(userId);
if (!userTodos || !userTodos.has(todoId)) {
throw new Error("Todo not found");
}
userTodos.delete(todoId);
// Don't delete from todos map if it's shared with others
const todo = this.todos.get(todoId);
if (todo && (!todo.sharedWith || todo.sharedWith.length === 0)) {
this.todos.delete(todoId);
}
console.log(`Todo deleted: ${todoId}`);
return true;
}
// Share a todo with another user
async shareTodo(userId, todoId, recipientId) {
// Validate users and todo
const senderClient = this.getUser(userId);
const recipientClient = this.getUser(recipientId);
const todo = this.todos.get(todoId);
if (!todo) {
throw new Error("Todo not found");
}
// Get the original text (in a real app, this would be re-encrypted for the recipient)
const originalText = todo.originalText;
if (!originalText) {
throw new Error("Cannot share this todo");
}
// Encrypt the todo for the recipient
const encryptedForRecipient = await recipientClient.encrypt(originalText);
// Create a new todo ID for the shared copy
const sharedTodoId = uuidv4();
// Store the shared todo
this.todos.set(sharedTodoId, {
id: sharedTodoId,
userId: recipientId,
encrypted: encryptedForRecipient,
createdAt: new Date().toISOString(),
originalText: originalText,
sharedBy: userId,
});
// Add to recipient's todos
this.userTodos.get(recipientId).add(sharedTodoId);
// Track sharing in the original todo
if (!todo.sharedWith) {
todo.sharedWith = [];
}
todo.sharedWith.push(recipientId);
console.log(
`Todo ${todoId} shared from ${userId} to ${recipientId} as ${sharedTodoId}`,
);
return sharedTodoId;
}
}