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: 2 additions & 3 deletions backend/migrations/004_create_projects_table.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ CREATE TABLE IF NOT EXISTS projects (
description TEXT NOT NULL,
status VARCHAR(50) NOT NULL CHECK (status IN ('proposal', 'ongoing', 'rejected')),
creator VARCHAR(42) NOT NULL,
owner_address VARCHAR(42) GENERATED ALWAYS AS (creator) STORED,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be careful not to modify existing migrations because this will cause a bug when deploying to prod. Always add new migration files

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a new migration was responsible for the errors, that was why the checks were not passing. But if I need to add new ones, I will have to work on it.

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_creator FOREIGN KEY (creator) REFERENCES profiles(address) ON DELETE CASCADE
);


CREATE INDEX IF NOT EXISTS idx_projects_creator ON projects(creator);
CREATE INDEX IF NOT EXISTS idx_projects_owner_address ON projects(owner_address);
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
CREATE INDEX IF NOT EXISTS idx_projects_created_at ON projects(created_at DESC);


CREATE OR REPLACE FUNCTION update_projects_updated_at()
RETURNS TRIGGER AS $$
BEGIN
Expand All @@ -23,7 +23,6 @@ BEGIN
END;
$$ LANGUAGE plpgsql;


CREATE TRIGGER trigger_update_projects_updated_at
BEFORE UPDATE ON projects
FOR EACH ROW
Expand Down
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": 1772197032165
},
"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