diff --git a/client/public/ERD.png b/client/public/ERD.png new file mode 100644 index 000000000..eff01ed00 Binary files /dev/null and b/client/public/ERD.png differ diff --git a/milestones/milestone2.md b/milestones/milestone2.md index e3178cd81..5f898953e 100644 --- a/milestones/milestone2.md +++ b/milestones/milestone2.md @@ -6,24 +6,29 @@ This document should be completed and submitted during **Unit 6** of this course This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets. -- [ ] In `planning/wireframes.md`: add wireframes for at least three pages in your web app. - - [ ] Include a list of pages in your app -- [ ] In `planning/entity_relationship_diagram.md`: add the entity relationship diagram you developed for your database. - - [ ] Your entity relationship diagram should include the tables in your database. -- [ ] Prepare your three-minute pitch presentation, to be presented during Unit 7 (the next unit). - - [ ] You do **not** need to submit any materials in advance of your pitch. -- [ ] In this document, complete all three questions in the **Reflection** section below +- [x] In `planning/wireframes.md`: add wireframes for at least three pages in your web app. + - [x] Include a list of pages in your app +- [x] In `planning/entity_relationship_diagram.md`: add the entity relationship diagram you developed for your database. + - [x] Your entity relationship diagram should include the tables in your database. +- [x] Prepare your three-minute pitch presentation, to be presented during Unit 7 (the next unit). + - [x] You do **not** need to submit any materials in advance of your pitch. +- [x] In this document, complete all three questions in the **Reflection** section below ## Reflection ### 1. What went well during this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +- Strong and consistent communication across Slack and Discord, with quick responses and active engagement from everyone +- Productive collaboration during live screen sharing sessions that allowed for real time feedback and problem solving +- Clear organization through GitHub Issues, which helped each member understand their tasks while staying aware of the overall project direction +- Easy scheduling thanks to a shared When2Meet that made coordinating weekly meetings simple +- A positive team culture built on supportive attitudes, willingness to listen, and reliable teamwork + ### 2. What were some challenges your group faced in this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +- Narrowing the platform’s many possibilities into a clear, shared MVP vision that aligned everyone on the core user experience. ### 3. What additional support will you need in upcoming units as you continue to work on your final project? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +- Constructive feedback from peers after our pitch, and continued guidance from our Tech Fellow as development progresses. diff --git a/planning/entity_relationship_diagram.md b/planning/entity_relationship_diagram.md index 12c25f62c..3bfd508c1 100644 --- a/planning/entity_relationship_diagram.md +++ b/planning/entity_relationship_diagram.md @@ -1,17 +1,38 @@ -# Entity Relationship Diagram +# Codefolio Entity Relationship Diagram (ERD) -Reference the Creating an Entity Relationship Diagram final project guide in the course portal for more information about how to complete this deliverable. +This ERD outlines the core structure of Codefolio’s backend, including user accounts, profiles, content, relationships, and tagging. It is organized into three sections: Entity Tables, Join Tables, and Utility Tables. -## Create the List of Tables -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ List each table in your diagram] -## Add the Entity Relationship Diagram +## List of Tables -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ Include an image or images of the diagram below. You may also wish to use the following markdown syntax to outline each table, as per your preference.] +🧱 **Entity Tables** - *Core Resources* +These tables represent the primary data models for users, content, and interactions across the platform. -| Column Name | Type | Description | -|-------------|------|-------------| -| id | integer | primary key | -| name | text | name of the shoe model | -| ... | ... | ... | +- **Users** - Handles authentication via GitHub OAuth and stores core account credentials and metadata. +- **Hashtags** - Stores reusable tags for skills, technologies, and post topics. +- **Profiles** - Public-facing profile information linked to each authenticated user. +- **Posts** - User-generated posts in the social feed, including media, descriptions, and links. +- **Projects** - Markdown-based showcases of a user’s projects, tech stack, and demos. +- **Network** - Tracks relationship requests between users (e.g., follow, connect). +- **Messages** - Records private messages exchanged between profiles, including sender, receiver, content, and read status. +- **Comments** - Stores comments made by users on posts. + + +πŸ”— **Join Tables** - *Many-to-Many Relationships* +These tables manage relational mappings between core entities, enabling flexible tagging and discovery features. + +- **profile_hashtags** - Connects profiles to hashtags to display skills and tech stack. +- **post_hashtags** - Connects posts to hashtags for topical discovery in the feed. + + +πŸ› οΈ **Utility Table** - *Supporting Feature* +This table supports additional platform functionality that enhances user experience. + +- **Bookmarks** - Stores a user’s saved posts or portfolio items. Includes constraints ensuring each bookmark references exactly one item and prevents duplicates. + + + +## Entity Relationship Diagram + +![Preview](../client/public/ERD.png) diff --git a/server/config/database.js b/server/config/database.js index 48c12c472..d26eca930 100644 --- a/server/config/database.js +++ b/server/config/database.js @@ -2,9 +2,9 @@ import pg from 'pg'; const config = { connectionString: process.env.DATABASE_URL, - ssl: { - rejectUnauthorized: false - } -} + ssl: false //{ + // rejectUnauthorized: false + // } +}; -export const pool = new pg.Pool(config) \ No newline at end of file +export const pool = new pg.Pool(config); diff --git a/server/config/reset.js b/server/config/reset.js index e69de29bb..1fe021496 100644 --- a/server/config/reset.js +++ b/server/config/reset.js @@ -0,0 +1,348 @@ +import './dotenv.js'; +import { pool } from './database.js'; +import hashtagData from '../data/hashtags.js'; + +//🧹 DROP All tables + +const dropTables = async () => { + const query =` + DROP TABLE IF EXISTS post_hashtags; + DROP TABLE IF EXISTS profile_hashtags; + DROP TABLE IF EXISTS comments; + DROP TABLE IF EXISTS bookmarks; + DROP TABLE IF EXISTS messages; + DROP TABLE IF EXISTS network; + DROP TABLE IF EXISTS projects; + DROP TABLE IF EXISTS posts; + DROP TABLE IF EXISTS profiles; + DROP TABLE IF EXISTS hashtags; + DROP TABLE IF EXISTS auth.users; + `; + + try { + await pool.query(query); + console.log('🧹 All tables dropped successfully!'); + } catch (err) { + console.error('⚠️Error dropping table:', err); + } +}; + + +/* πŸͺ„ Create all tables */ + +//Table 1 of 11 - Authorized Users +const createUsersTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email TEXT UNIQUE, + encrypted_password TEXT, + github_id TEXT UNIQUE, + provider TEXT DEFAULT 'github', + created_at TIMESTAMP DEFAULT now() + ); + `; + + try { + await pool.query(query); + console.log('Users table created successfully'); + } catch (err) { + console.error('Error creating Users table:', err); + } +}; + +//Table 2 of 11 - Hashtags +const createHashtagsTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS hashtags ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tag_text TEXT UNIQUE, + category TEXT, + created_at TIMESTAMP DEFAULT now() + ); + + -- Function: auto-convert tags to lowercase before saving + CREATE OR REPLACE FUNCTION tag_case() + RETURNS TRIGGER AS $$ + BEGIN + NEW.tag_text := LOWER(NEW.tag_text); + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + -- Trigger: applies lowercase conversion before insert or update + CREATE TRIGGER tag_case_trigger + BEFORE INSERT OR UPDATE ON hashtags + FOR EACH ROW EXECUTE FUNCTION tag_case(); + `; + + try { + await pool.query(query); + console.log('Hashtags table created successfully'); + } catch (err) { + console.error('Error creating hashtags table:', err); + } +}; + +//Table 3 of 11 - Profiles +const createProfilesTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id), + username TEXT UNIQUE, + bio TEXT, + location TEXT, + avatar_url TEXT, + links TEXT, + created_at TIMESTAMP DEFAULT now() + ); + `; + + try { + await pool.query(query); + console.log('Profiles table created successfully'); + } catch (err) { + console.error('Error creating profiles table:', err); + } +}; + +//Table 4 of 11 - Posts +const createPostsTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS posts ( + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + profile_id UUID REFERENCES profiles(id), + title TEXT, + description TEXT, + media_url TEXT, + link TEXT, + likes_count INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT now() + ); + `; + + try { + await pool.query(query); + console.log('Posts table created successfully'); + } catch (err) { + console.error('Error creating posts table:', err); + } +}; + +//Table 5 of 11 - Projects +const createProjectsTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS projects ( + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + profile_id UUID REFERENCES profiles(id), + title TEXT, + description TEXT, + tech_stack TEXT, + demo_url TEXT, + collaborators TEXT, + links TEXT, + license TEXT, + md_content TEXT, + created_at TIMESTAMP DEFAULT now() + ); + `; + + try { + await pool.query(query); + console.log('Projects table created successfully'); + } catch (err) { + console.error('Error creating projects table:', err); + } +}; + +//Table 6 of 11 - Network +const createNetworkTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS network ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + requester_id UUID REFERENCES profiles(id), + receiver_id UUID REFERENCES profiles(id), + status TEXT CHECK (status IN ('pending', 'accepted', 'rejected')), + created_at TIMESTAMP DEFAULT now() + ); + `; + + try { + await pool.query(query); + console.log('Network table created successfully'); + } catch (err) { + console.error('Error creating network table:', err); + } +}; + +//Table 7 of 11 - Messages +const createMessagesTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + sender_id UUID REFERENCES profiles(id), + receiver_id UUID REFERENCES profiles(id), + status TEXT CHECK (status IN ('read', 'unread')), + content TEXT, + created_at TIMESTAMP DEFAULT now() + ); + `; + + try { + await pool.query(query); + console.log('Messages table created successfully'); + } catch (err) { + console.error('Error creating messages table:', err); + } +}; + +//Table 8 of 11 - Bookmarks +const createBookmarksTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS bookmarks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + profile_id UUID REFERENCES profiles(id), + post_id INTEGER REFERENCES posts(id), + project_id INTEGER REFERENCES projects(id), + created_at TIMESTAMP DEFAULT now(), + CHECK ( + (post_id IS NOT NULL AND project_id IS NULL) + OR (post_id IS NULL AND project_id IS NOT NULL) + ), + UNIQUE (profile_id, post_id), + UNIQUE (profile_id, project_id) + ); + `; + + try { + await pool.query(query); + console.log('Bookmarks table created successfully'); + } catch (err) { + console.error('Error creating bookmarks table:', err); + } +}; + +//Table 9 of 11 - Comments +const createCommentsTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS comments ( + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + post_id INTEGER REFERENCES posts(id), + profile_id UUID REFERENCES profiles(id), + content TEXT, + created_at TIMESTAMP DEFAULT now(), + UNIQUE (post_id, profile_id, content) + ); + `; + + try { + await pool.query(query); + console.log('Comments table created successfully'); + } catch (err) { + console.error('Error creating comments table:', err); + } +}; + +//Table 10 of 11 - Profile Hashtags +const createProfileHashtagsTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS profile_hashtags ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + profile_id UUID REFERENCES profiles(id), + hashtag_id UUID REFERENCES hashtags(id) + ); + `; + + try { + await pool.query(query); + console.log('Profile hashtags table created successfully'); + } catch (err) { + console.error('Error creating profile hashtags table:', err); + } +}; + +//Table 11 of 11 - Post Hashtags +const createPostHashtagsTable = async () => { + + const query = ` + CREATE TABLE IF NOT EXISTS post_hashtags ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + post_id INTEGER REFERENCES posts(id), + hashtag_id UUID REFERENCES hashtags(id) + ); + `; + + try { + await pool.query(query); + console.log('Post hashtags table created successfully'); + } catch (err) { + console.error('Error creating post hashtags table:', err); + } +}; + + + +/* 🌱 Seed tables */ + +//Table 2: Hashtags +const seedHashtagsTable = async () => { + const client = await pool.connect(); + + try{ + for (const hashtag of hashtagData) { + const insertQuery = ` + INSERT INTO hashtags (tag_text, category) + VALUES ($1, $2) + `; + + const values = [ + hashtag.tag_text, + hashtag.category + ]; + + await client.query(insertQuery, values); + console.log(`Hashtag added successfully: ${hashtag.tag_text}!`); + } + } catch (err) { + console.error('Error inserting hashtag:', err); + } finally { + client.release(); + } +}; + +//Reset function to drop, create, and seed tables in the correct order +const resetDatabase = async () => { + await dropTables(); + + await createUsersTable(); + await createHashtagsTable(); + await createProfilesTable(); + await createPostsTable(); + await createProjectsTable(); + await createNetworkTable(); + await createMessagesTable(); + await createBookmarksTable(); + await createCommentsTable(); + await createProfileHashtagsTable(); + await createPostHashtagsTable(); + + await seedHashtagsTable(); + + console.log('πŸŽ‰ Database reset complete!'); + + //close all connections once scrip has run + await pool.end(); +} + +resetDatabase(); diff --git a/server/data/hashtags.js b/server/data/hashtags.js new file mode 100644 index 000000000..82be946b2 --- /dev/null +++ b/server/data/hashtags.js @@ -0,0 +1,124 @@ +const hashtagData = [ + // Languages + { tag_text: "javascript", category: "language" }, + { tag_text: "typescript", category: "language" }, + { tag_text: "python", category: "language" }, + { tag_text: "java", category: "language" }, + { tag_text: "csharp", category: "language" }, + { tag_text: "cpp", category: "language" }, + { tag_text: "go", category: "language" }, + { tag_text: "rust", category: "language" }, + { tag_text: "ruby", category: "language" }, + { tag_text: "php", category: "language" }, + { tag_text: "swift", category: "language" }, + { tag_text: "kotlin", category: "language" }, + + // Frontend Core + { tag_text: "html", category: "frontend" }, + { tag_text: "css", category: "frontend" }, + { tag_text: "sass", category: "frontend" }, + { tag_text: "tailwind", category: "frontend" }, + { tag_text: "bootstrap", category: "frontend" }, + { tag_text: "materialui", category: "frontend" }, + { tag_text: "chakraui", category: "frontend" }, + { tag_text: "framer-motion", category: "frontend" }, + + // Frontend Frameworks + { tag_text: "react", category: "framework" }, + { tag_text: "nextjs", category: "framework" }, + { tag_text: "vue", category: "framework" }, + { tag_text: "nuxt", category: "framework" }, + { tag_text: "svelte", category: "framework" }, + { tag_text: "astro", category: "framework" }, + { tag_text: "angular", category: "framework" }, + + // Backend Frameworks + { tag_text: "nodejs", category: "framework" }, + { tag_text: "express", category: "framework" }, + { tag_text: "fastapi", category: "framework" }, + { tag_text: "django", category: "framework" }, + { tag_text: "flask", category: "framework" }, + { tag_text: "springboot", category: "framework" }, + { tag_text: "laravel", category: "framework" }, + { tag_text: "rails", category: "framework" }, + + // Databases + { tag_text: "postgresql", category: "database" }, + { tag_text: "mysql", category: "database" }, + { tag_text: "mongodb", category: "database" }, + { tag_text: "sqlite", category: "database" }, + { tag_text: "redis", category: "database" }, + { tag_text: "neon", category: "database" }, + { tag_text: "supabase", category: "database" }, + { tag_text: "prisma", category: "database" }, + + // DevOps / Cloud + { tag_text: "docker", category: "cloud" }, + { tag_text: "kubernetes", category: "cloud" }, + { tag_text: "aws", category: "cloud" }, + { tag_text: "azure", category: "cloud" }, + { tag_text: "gcp", category: "cloud" }, + { tag_text: "vercel", category: "cloud" }, + { tag_text: "netlify", category: "cloud" }, + { tag_text: "ci-cd", category: "cloud" }, + { tag_text: "linux", category: "cloud" }, + + // Tools + { tag_text: "git", category: "tool" }, + { tag_text: "github", category: "tool" }, + { tag_text: "vscode", category: "tool" }, + { tag_text: "postman", category: "tool" }, + { tag_text: "figma", category: "tool" }, + { tag_text: "webpack", category: "tool" }, + { tag_text: "vite", category: "tool" }, + { tag_text: "eslint", category: "tool" }, + { tag_text: "prettier", category: "tool" }, + + // AI / ML Concepts + { tag_text: "machinelearning", category: "ai-ml" }, + { tag_text: "deeplearning", category: "ai-ml" }, + { tag_text: "tensorflow", category: "ai-ml" }, + { tag_text: "pytorch", category: "ai-ml" }, + { tag_text: "scikitlearn", category: "ai-ml" }, + { tag_text: "nlp", category: "ai-ml" }, + { tag_text: "computer-vision", category: "ai-ml" }, + + // AI Tools + { tag_text: "chatgpt", category: "ai-tools" }, + { tag_text: "claude", category: "ai-tools" }, + { tag_text: "gemini", category: "ai-tools" }, + { tag_text: "grok", category: "ai-tools" }, + { tag_text: "midjourney", category: "ai-tools" }, + { tag_text: "stable-diffusion", category: "ai-tools" }, + { tag_text: "runwayml", category: "ai-tools" }, + { tag_text: "elevenlabs", category: "ai-tools" }, + { tag_text: "openai-api", category: "ai-tools" }, + { tag_text: "anthropic-api", category: "ai-tools" }, + + // Mobile + { tag_text: "reactnative", category: "mobile" }, + { tag_text: "flutter", category: "mobile" }, + { tag_text: "android", category: "mobile" }, + { tag_text: "ios", category: "mobile" }, + + // Creative Tech + { tag_text: "threejs", category: "creative-tech" }, + { tag_text: "webgl", category: "creative-tech" }, + { tag_text: "canvas", category: "creative-tech" }, + { tag_text: "uiux", category: "creative-tech" }, + { tag_text: "accessibility", category: "creative-tech" }, + { tag_text: "responsive-design", category: "creative-tech" }, + + // Career / Skill Level + { tag_text: "beginner", category: "career-level" }, + { tag_text: "intermediate", category: "career-level" }, + { tag_text: "advanced", category: "career-level" }, + + // Roles + { tag_text: "fullstack", category: "role" }, + { tag_text: "frontend", category: "role" }, + { tag_text: "backend", category: "role" }, + { tag_text: "devops", category: "role" } +]; + +export default hashtagData; diff --git a/server/package.json b/server/package.json index 94dfbccd4..aa0417e54 100644 --- a/server/package.json +++ b/server/package.json @@ -5,8 +5,10 @@ "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "nodemon server.js", - "start": "node server.js" + "dev": "concurrently \"cd client && vite\" \"cd server && nodemon --require dotenv/config server.js\"", + "reset": "node config/reset.js", + "start": "npm run reset && node server/server.js", + "build": "cd client && vite build" }, "keywords": [], "author": "",