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
5 changes: 5 additions & 0 deletions backend/migrations/006_add_owner_address_to_projects.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE projects
ADD COLUMN IF NOT EXISTS owner_address VARCHAR(42)
GENERATED ALWAYS AS (creator) STORED;

CREATE INDEX IF NOT EXISTS idx_projects_owner_address ON projects(owner_address);
6 changes: 4 additions & 2 deletions backend/src/application/commands/create_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ pub async fn create_project(
.await
.map_err(|e| e.to_string())?;

// Return response
let creator_str = project.creator.to_string();

Ok(ProjectResponse {
id: project.id.value().to_string(),
name: project.name,
description: project.description,
status: project.status,
creator: project.creator.to_string(),
owner_address: creator_str.clone(),
creator: creator_str,
created_at: project.created_at,
updated_at: project.updated_at,
})
Expand Down
13 changes: 4 additions & 9 deletions backend/src/application/commands/update_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@ pub async fn update_project(
project_id: String,
request: UpdateProjectRequest,
) -> Result<ProjectResponse, String> {
// Parse project ID
let id = Uuid::parse_str(&project_id).map_err(|_| "Invalid project ID".to_string())?;
let project_id = ProjectId::from_uuid(id);

// Validate requester address
let requester = WalletAddress::new(requester_address)
.map_err(|e| format!("Invalid wallet address: {}", e))?;

// Get existing project
let mut project = repository
.find_by_id(&project_id)
.await
Expand All @@ -35,25 +32,23 @@ pub async fn update_project(
return Err("Only the creator can update this project".to_string());
}

// Update project
project.update_info(request.name, request.description, request.status);

// Validate updated project
project.validate()?;

// Save to repository
repository
.update(&project)
.await
.map_err(|e| e.to_string())?;

// Return response
let creator_str = project.creator.to_string();

Ok(ProjectResponse {
id: project.id.value().to_string(),
name: project.name,
description: project.description,
status: project.status,
creator: project.creator.to_string(),
owner_address: creator_str.clone(),
creator: creator_str,
created_at: project.created_at,
updated_at: project.updated_at,
})
Expand Down
1 change: 1 addition & 0 deletions backend/src/application/dtos/project_dtos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub struct ProjectResponse {
pub description: String,
pub status: ProjectStatus,
pub creator: String,
pub owner_address: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
25 changes: 12 additions & 13 deletions backend/src/application/queries/get_all_projects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ pub async fn get_all_projects(
limit: Option<i64>,
offset: Option<i64>,
) -> Result<Vec<ProjectResponse>, String> {
// Parse status if provided
let status_filter = if let Some(status_str) = status {
Some(
status_str
Expand All @@ -26,7 +25,6 @@ pub async fn get_all_projects(
None
};

// Parse creator if provided
let creator_filter = if let Some(creator_str) = creator {
Some(
WalletAddress::new(creator_str)
Expand All @@ -36,27 +34,28 @@ pub async fn get_all_projects(
None
};

// Validate and limit pagination
let limit = limit.map(|l| l.clamp(1, 100));
let offset = offset.map(|o| o.max(0));

// Get projects
let projects = repository
.find_all(status_filter, creator_filter.as_ref(), limit, offset)
.await
.map_err(|e| e.to_string())?;

// Convert to responses
Ok(projects
.into_iter()
.map(|project| ProjectResponse {
id: project.id.value().to_string(),
name: project.name,
description: project.description,
status: project.status,
creator: project.creator.to_string(),
created_at: project.created_at,
updated_at: project.updated_at,
.map(|project| {
let creator_str = project.creator.to_string();
ProjectResponse {
id: project.id.value().to_string(),
name: project.name,
description: project.description,
status: project.status,
owner_address: creator_str.clone(),
creator: creator_str,
created_at: project.created_at,
updated_at: project.updated_at,
}
})
.collect())
}
8 changes: 4 additions & 4 deletions backend/src/application/queries/get_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@ pub async fn get_project(
repository: Arc<dyn ProjectRepository>,
project_id: String,
) -> Result<ProjectResponse, String> {
// Parse project ID
let id = Uuid::parse_str(&project_id).map_err(|_| "Invalid project ID".to_string())?;
let project_id = ProjectId::from_uuid(id);

// Get project
let project = repository
.find_by_id(&project_id)
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| "Project not found".to_string())?;

// Return response
let creator_str = project.creator.to_string();

Ok(ProjectResponse {
id: project.id.value().to_string(),
name: project.name,
description: project.description,
status: project.status,
creator: project.creator.to_string(),
owner_address: creator_str.clone(),
creator: creator_str,
created_at: project.created_at,
updated_at: project.updated_at,
})
Expand Down
23 changes: 12 additions & 11 deletions backend/src/application/queries/get_projects_by_creator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@ pub async fn get_projects_by_creator(
repository: Arc<dyn ProjectRepository>,
creator_address: String,
) -> Result<Vec<ProjectResponse>, String> {
// Validate creator address
let creator = WalletAddress::new(creator_address)
.map_err(|e| format!("Invalid wallet address: {}", e))?;

// Get projects
let projects = repository
.find_by_creator(&creator)
.await
.map_err(|e| e.to_string())?;

// Convert to responses
Ok(projects
.into_iter()
.map(|project| ProjectResponse {
id: project.id.value().to_string(),
name: project.name,
description: project.description,
status: project.status,
creator: project.creator.to_string(),
created_at: project.created_at,
updated_at: project.updated_at,
.map(|project| {
let creator_str = project.creator.to_string();
ProjectResponse {
id: project.id.value().to_string(),
name: project.name,
description: project.description,
status: project.status,
owner_address: creator_str.clone(),
creator: creator_str,
created_at: project.created_at,
updated_at: project.updated_at,
}
})
.collect())
}
2 changes: 1 addition & 1 deletion frontend/.astro/data-store.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.14.1","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":\"0.0.0.0\",\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\",\"entrypoint\":\"astro/assets/endpoint/node\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false},\"legacy\":{\"collections\":false},\"session\":{\"driver\":\"fs-lite\",\"options\":{\"base\":\"/app/node_modules/.astro/sessions\"}}}"]
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.14.1","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":\"0.0.0.0\",\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\",\"entrypoint\":\"astro/assets/endpoint/node\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false},\"legacy\":{\"collections\":false},\"session\":{\"driver\":\"fs-lite\",\"options\":{\"base\":\"/home/phoebe/TheGuildGenesis/frontend/node_modules/.astro/sessions\"}}}"]
2 changes: 1 addition & 1 deletion frontend/.astro/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_variables": {
"lastUpdateCheck": 1770638442880
"lastUpdateCheck": 1770210836100
},
"eslint.validate": [
"javascript",
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/components/AppSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Smile, BadgeCheck, Home, BookOpen , Table2 } from "lucide-react";
import { Smile, BadgeCheck, Home, BookOpen , Table2, FolderKanban } from "lucide-react";

import {
Sidebar,
Expand Down Expand Up @@ -38,6 +38,11 @@ const items = [
url: "/leaderboard",
icon: Table2,
},
{
title: "Projects",
url: "/projects",
icon: FolderKanban,
},
];

export function AppSidebar() {
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/components/pages/ProjectPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AppWrapper } from "@/components/AppWrapper";
import { ProjectMain } from "@/components/projects/project-page/ProjectMain";

type Props = { id?: string };

export default function ProjectPage({ id }: Props) {
return (
<AppWrapper>
<ProjectMain id={id || ""} />
</AppWrapper>
);
}
20 changes: 20 additions & 0 deletions frontend/src/components/pages/ProjectsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AppWrapper } from "@/components/AppWrapper";
import { ProjectsMain } from "@/components/projects/ProjectsMain";

export function ProjectsPage() {
return (
<AppWrapper>
<section className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-10">
<h1 className="mb-6 text-3xl font-bold tracking-tight text-gray-900">
Projects
</h1>
<p className="mb-8 max-w-2xl text-gray-600">
Showcase your work. Projects can be created by anyone and are managed by their owners.
</p>
<ProjectsMain />
</section>
</AppWrapper>
);
}

export default ProjectsPage;
Loading
Loading