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
10 changes: 10 additions & 0 deletions apps/api/db/migrations/006_extend_users.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Extends the users table with profile-specific fields not yet present
ALTER TABLE users
ADD COLUMN first_name VARCHAR(50),
ADD COLUMN role VARCHAR(50),
ADD COLUMN location VARCHAR(100),
ADD COLUMN university VARCHAR(100),
ADD COLUMN time_zone VARCHAR(50),
ADD COLUMN last_active_at TIMESTAMPTZ,
ADD COLUMN gender VARCHAR(50),
ADD COLUMN languages TEXT[]; -- Simple text array for display purposes on the profile
10 changes: 10 additions & 0 deletions apps/api/db/migrations/007_profile_stats.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Cached aggregate coding data to avoid heavy calculations on every profile view
-- This will be populated and updated by the background session-processing job
CREATE TABLE user_profile_stats (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
total_coding_seconds BIGINT DEFAULT 0,
total_active_days INTEGER DEFAULT 0,
current_streak INTEGER DEFAULT 0,
max_streak INTEGER DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT now()
);
18 changes: 18 additions & 0 deletions apps/api/db/migrations/008_achievements.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- A richer achievement system linking users to specific badges and their metadata
CREATE TABLE achievement_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key VARCHAR(50) UNIQUE NOT NULL,
title VARCHAR(100) NOT NULL,
description TEXT NOT NULL,
badge_class VARCHAR(50) NOT NULL
);

CREATE TABLE user_achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
achievement_type_id UUID REFERENCES achievement_types(id) ON DELETE CASCADE,
earned_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (user_id, achievement_type_id)
);

CREATE INDEX idx_user_achievements_user_id ON user_achievements(user_id);
15 changes: 15 additions & 0 deletions apps/api/db/migrations/009_activity_log.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- Activity log for the recent activity feed on a user's profile
CREATE TABLE activity_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
kind VARCHAR(50) NOT NULL,
text TEXT NOT NULL,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_activity_log_user_time ON activity_log(user_id, created_at DESC);

-- Also add index on sessions and heartbeats as requested in the plan
CREATE INDEX idx_sessions_user_time ON sessions(user_id, start_time);
CREATE INDEX idx_heartbeats_user_time ON heartbeats(user_id, time);
14 changes: 14 additions & 0 deletions apps/api/db/migrations/010_problem_stats_placeholder.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Placeholder table for problem-solving stats
-- WARNING: There is currently no problem-solving or contest domain in Seismic.
-- This schema is intentionally introduced only to satisfy the future Profile API
-- and remains unused until such a feature exists. Ratings and contributions are
-- strictly placeholders and not derived from any existing system.
CREATE TABLE user_problem_stats (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
solved_count INTEGER DEFAULT 0,
total_problems INTEGER DEFAULT 0,
attempting_count INTEGER DEFAULT 0,
rating INTEGER DEFAULT 0,
contribution_points INTEGER DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT now()
);
56 changes: 56 additions & 0 deletions apps/api/db/seed_profile.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
-- Ensure we have a user to attach these to
DO $$
DECLARE
test_user_id UUID;
ach_id UUID;
BEGIN
-- Get or create a test user
SELECT id INTO test_user_id FROM users WHERE username = 'testuser' LIMIT 1;

IF NOT FOUND THEN
INSERT INTO users (username, email, display_name, first_name, role, location, university, bio, time_zone, gender, languages)
VALUES ('testuser', 'testuser@example.com', 'Test User', 'Test', 'Developer', 'San Francisco, CA', 'Tech University', 'Just a test user building cool things.', 'America/Los_Angeles', 'Male', ARRAY['Go', 'TypeScript', 'SQL'])
RETURNING id INTO test_user_id;
END IF;

-- Upsert Profile Stats
INSERT INTO user_profile_stats (user_id, total_coding_seconds, total_active_days, current_streak, max_streak)
VALUES (test_user_id, 360000, 45, 12, 15)
ON CONFLICT (user_id) DO UPDATE SET
total_coding_seconds = EXCLUDED.total_coding_seconds,
total_active_days = EXCLUDED.total_active_days,
current_streak = EXCLUDED.current_streak,
max_streak = EXCLUDED.max_streak;

-- Upsert Problem Stats (Placeholder)
INSERT INTO user_problem_stats (user_id, solved_count, total_problems, attempting_count, rating, contribution_points)
VALUES (test_user_id, 150, 500, 3, 1450, 200)
ON CONFLICT (user_id) DO UPDATE SET
solved_count = EXCLUDED.solved_count,
total_problems = EXCLUDED.total_problems,
attempting_count = EXCLUDED.attempting_count,
rating = EXCLUDED.rating,
contribution_points = EXCLUDED.contribution_points;

-- Seed Achievement Types
INSERT INTO achievement_types (key, title, description, badge_class)
VALUES
('first_blood', 'First Blood', 'Logged your first session.', 'gold'),
('night_owl', 'Night Owl', 'Coded past 2 AM.', 'silver'),
('streak_7', '7-Day Streak', 'Maintained a 7 day streak.', 'bronze')
ON CONFLICT (key) DO NOTHING;

-- Give the user an achievement
SELECT id INTO ach_id FROM achievement_types WHERE key = 'first_blood' LIMIT 1;
IF ach_id IS NOT NULL THEN
INSERT INTO user_achievements (user_id, achievement_type_id)
VALUES (test_user_id, ach_id)
ON CONFLICT (user_id, achievement_type_id) DO NOTHING;
END IF;

-- Seed Activity Log
INSERT INTO activity_log (user_id, kind, text)
VALUES (test_user_id, 'achievement', 'Earned the First Blood badge!'),
(test_user_id, 'streak', 'Reached a 12 day streak!');

END $$;
211 changes: 211 additions & 0 deletions apps/api/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,40 @@
}
}
},
<<<<<<< HEAD
=======
"/api/profile": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Returns the logged-in user's full profile: bio info, coding metrics, activity heatmap, problem-solving gauge, achievements, recent activity, and personal-info completion.",
"produces": [
"application/json"
],
"tags": [
"profile"
],
"summary": "Get full profile",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/helpers.APIResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/helpers.APIResponse"
}
}
}
}
},
>>>>>>> cb826e2 (feat: add user profile page and improve Swagger API documentation)
"/api/settings/account": {
"post": {
"security": [
Expand Down Expand Up @@ -815,6 +849,43 @@
}
}
},
<<<<<<< HEAD
=======
"models.Achievement": {
"type": "object",
"properties": {
"badgeClass": {
"type": "string"
},
"description": {
"type": "string"
},
"earnedAt": {
"type": "string"
},
"key": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"models.ActivityLogItem": {
"type": "object",
"properties": {
"at": {
"type": "string"
},
"kind": {
"type": "string"
},
"text": {
"type": "string"
}
}
},
>>>>>>> cb826e2 (feat: add user profile page and improve Swagger API documentation)
"models.Heartbeat": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -852,6 +923,146 @@
"type": "string"
}
}
<<<<<<< HEAD
=======
},
"models.HeatmapCell": {
"type": "object",
"properties": {
"date": {
"type": "string"
},
"level": {
"type": "integer"
}
}
},
"models.ProfileInfoField": {
"type": "object",
"properties": {
"completed": {
"type": "boolean"
},
"key": {
"type": "string"
},
"label": {
"type": "string"
}
}
},
"models.ProfileMetric": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"label": {
"type": "string"
},
"positive": {
"type": "boolean"
},
"sub": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"models.ProfileResponse": {
"type": "object",
"properties": {
"achievements": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Achievement"
}
},
"attempting": {
"type": "integer"
},
"avatarUrl": {
"type": "string"
},
"bio": {
"type": "string"
},
"completionPercent": {
"type": "integer"
},
"email": {
"type": "string"
},
"firstName": {
"type": "string"
},
"heatmap": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/models.HeatmapCell"
}
}
},
"infoFields": {
"type": "array",
"items": {
"$ref": "#/definitions/models.ProfileInfoField"
}
},
"joinDate": {
"type": "string"
},
"lastActive": {
"type": "string"
},
"location": {
"type": "string"
},
"maxStreak": {
"type": "integer"
},
"memberFor": {
"type": "string"
},
"metrics": {
"type": "array",
"items": {
"$ref": "#/definitions/models.ProfileMetric"
}
},
"recentActivity": {
"type": "array",
"items": {
"$ref": "#/definitions/models.ActivityLogItem"
}
},
"role": {
"type": "string"
},
"solved": {
"type": "integer"
},
"timeZone": {
"type": "string"
},
"totalActiveDays": {
"type": "integer"
},
"totalProblems": {
"type": "integer"
},
"university": {
"type": "string"
},
"username": {
"type": "string"
}
}
>>>>>>> cb826e2 (feat: add user profile page and improve Swagger API documentation)
}
},
"securityDefinitions": {
Expand Down
Loading