{"id":18141358,"url":"https://github.com/aryaniyaps/qa-forum","last_synced_at":"2026-04-12T00:02:26.735Z","repository":{"id":260495762,"uuid":"876188524","full_name":"aryaniyaps/qa-forum","owner":"aryaniyaps","description":"Anonymous QA Forum (Powered by GraphQL and Relay)","archived":false,"fork":false,"pushed_at":"2025-01-22T04:01:21.000Z","size":967,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-13T00:28:38.502Z","etag":null,"topics":["docker","docker-compose","fastapi","graphql","postgresql","python","react","relay","strawberry-graphql","vite"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aryaniyaps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-21T14:44:21.000Z","updated_at":"2024-12-21T06:34:35.000Z","dependencies_parsed_at":"2024-12-20T05:41:47.745Z","dependency_job_id":"ecc61b08-fac1-4b81-b5ff-8695dad9a20d","html_url":"https://github.com/aryaniyaps/qa-forum","commit_stats":null,"previous_names":["aryaniyaps/qa-forum"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aryaniyaps%2Fqa-forum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aryaniyaps%2Fqa-forum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aryaniyaps%2Fqa-forum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aryaniyaps%2Fqa-forum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aryaniyaps","download_url":"https://codeload.github.com/aryaniyaps/qa-forum/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247528744,"owners_count":20953475,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","docker-compose","fastapi","graphql","postgresql","python","react","relay","strawberry-graphql","vite"],"created_at":"2024-11-01T17:06:29.900Z","updated_at":"2026-04-12T00:02:26.620Z","avatar_url":"https://github.com/aryaniyaps.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# QA Forum (Anonymous)\n\nhttps://github.com/user-attachments/assets/c1b72eac-6a8d-40ba-a6c7-861438c0484b\n\n\u003e QA Forum is an anonymous question-answer platform where users can post questions, provide answers, and upvote or downvote questions. The app ensures user anonymity through browser fingerprinting, which uniquely identifies users without the need for personal information.\n\n## Features\n\n- **Anonymous Questions and Answers**: Users can anonymously post questions and respond with answers.\n- **Upvote/Downvote Mechanism**: Vote on questions to highlight useful content.\n- **User Identification via Browser Fingerprinting**: No personal data is required; users are identified solely by a unique fingerprint.\n- **Automated Audit Logging**: User actions are logged using PostgreSQL functions and triggers.\n\n## Tech Stack\n\n- **Backend**: PostgreSQL, Python, FastAPI, GraphQL\n- **Frontend**: React, Relay, TypeScript\n- **Containerization**: Docker Compose for managing PostgreSQL\n\n## Database Schema\n\n```sql\n-- Table for audit logs\nCREATE TABLE audit_logs (\n    id SERIAL PRIMARY KEY,\n    table_name VARCHAR NOT NULL,\n    operation VARCHAR NOT NULL,\n    row_id INTEGER NOT NULL,\n    old_data JSONB,\n    new_data JSONB,\n    created_at TIMESTAMP DEFAULT now()\n);\n\n-- Users table\nCREATE TABLE users (\n    id SERIAL PRIMARY KEY,\n    fingerprint TEXT NOT NULL,\n    username VARCHAR(8) NOT NULL,\n    created_at TIMESTAMP DEFAULT now(),\n    updated_at TIMESTAMP\n);\n\n-- Questions table\nCREATE TABLE questions (\n    id SERIAL PRIMARY KEY,\n    user_id INTEGER NOT NULL,\n    title CITEXT NOT NULL,  -- CITEXT doesn't require a length limit\n    description TEXT NOT NULL,\n    created_at TIMESTAMP DEFAULT now(),\n    updated_at TIMESTAMP,\n    FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE\n);\n\n-- Answers table\nCREATE TABLE answers (\n    id SERIAL PRIMARY KEY,\n    user_id INTEGER NOT NULL,\n    question_id INTEGER NOT NULL,\n    content TEXT NOT NULL,\n    created_at TIMESTAMP DEFAULT now(),\n    updated_at TIMESTAMP,\n    FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,\n    FOREIGN KEY (question_id) REFERENCES questions (id) ON DELETE CASCADE\n);\n\n-- Enum type for vote type\nCREATE TYPE vote_type AS ENUM ('UPVOTE', 'DOWNVOTE');\n\n-- Question votes table\nCREATE TABLE question_votes (\n    user_id INTEGER NOT NULL,\n    question_id INTEGER NOT NULL,\n    vote_type vote_type NOT NULL,\n    PRIMARY KEY (user_id, question_id),\n    FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,\n    FOREIGN KEY (question_id) REFERENCES questions (id) ON DELETE CASCADE\n);\n\n-- Audit log function and trigger for questions\nCREATE OR REPLACE FUNCTION log_question_changes()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF TG_OP = 'INSERT' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, new_data, created_at)\n        VALUES ('questions', 'INSERT', NEW.id, row_to_json(NEW), NOW());\n    ELSIF TG_OP = 'UPDATE' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, old_data, new_data, created_at)\n        VALUES ('questions', 'UPDATE', OLD.id, row_to_json(OLD), row_to_json(NEW), NOW());\n    ELSIF TG_OP = 'DELETE' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, old_data, created_at)\n        VALUES ('questions', 'DELETE', OLD.id, row_to_json(OLD), NOW());\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER question_audit_trigger\nAFTER INSERT OR UPDATE OR DELETE ON questions\nFOR EACH ROW\nEXECUTE FUNCTION log_question_changes();\n\n-- Audit log function and trigger for question_votes\nCREATE OR REPLACE FUNCTION log_question_vote_changes()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF TG_OP = 'INSERT' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, new_data, created_at)\n        VALUES ('question_votes', 'INSERT', NEW.question_id, row_to_json(NEW), NOW());\n    ELSIF TG_OP = 'UPDATE' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, old_data, new_data, created_at)\n        VALUES ('question_votes', 'UPDATE', OLD.question_id, row_to_json(OLD), row_to_json(NEW), NOW());\n    ELSIF TG_OP = 'DELETE' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, old_data, created_at)\n        VALUES ('question_votes', 'DELETE', OLD.question_id, row_to_json(OLD), NOW());\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER question_vote_audit_trigger\nAFTER INSERT OR UPDATE OR DELETE ON question_votes\nFOR EACH ROW\nEXECUTE FUNCTION log_question_vote_changes();\n\n-- Audit log function and trigger for answers\nCREATE OR REPLACE FUNCTION log_answer_changes()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF TG_OP = 'INSERT' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, new_data, created_at)\n        VALUES ('answers', 'INSERT', NEW.id, row_to_json(NEW), NOW());\n    ELSIF TG_OP = 'UPDATE' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, old_data, new_data, created_at)\n        VALUES ('answers', 'UPDATE', OLD.id, row_to_json(OLD), row_to_json(NEW), NOW());\n    ELSIF TG_OP = 'DELETE' THEN\n        INSERT INTO audit_logs (table_name, operation, row_id, old_data, created_at)\n        VALUES ('answers', 'DELETE', OLD.id, row_to_json(OLD), NOW());\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER answer_audit_trigger\nAFTER INSERT OR UPDATE OR DELETE ON answers\nFOR EACH ROW\nEXECUTE FUNCTION log_answer_changes();\n```\n\n## Getting Started\n\n### Prerequisites\n\n- Docker and Docker Compose\n- PDM for Python package management\n- Node.js and PNPM (for the React frontend)\n\n### Installation\n\n1. **Clone the repository**:\n\n   ```bash\n   git clone https://github.com/aryaniyaps/qa-forum\n   ```\n\n2. **Set up PostgreSQL with Docker Compose**:\n\n   ```bash\n   docker-compose up -d\n   ```\n\n   This will start a PostgreSQL container configured for the project.\n\n3. **Install backend dependencies and start the server**:\n\n   - Navigate to the server directory:\n\n     ```bash\n     cd server\n     ```\n\n   - Create an `.env` file following the reference template `./.env.example`.\n\n   - Install dependencies:\n     ```bash\n     pdm install\n     ```\n   - Generate the GraphQL schema:\n     ```bash\n     pdm run generate-graphql-schema\n     ```\n   - Running the persisted queries server:\n     ```bash\n     pdm run persist-server\n     ```\n   - Start the backend server:\n     ```bash\n     pdm run dev\n     ```\n\n4. **Install frontend dependencies and start the client**:\n\n   - Navigate to the client directory:\n\n     ```bash\n     cd client\n     ```\n\n   - Create an `.env` file following the reference template `./.env.example`.\n\n   - Install dependencies:\n     ```bash\n     pnpm install\n     ```\n   - Run the Vite development server:\n     ```bash\n     pnpm run dev\n     ```\n   - Run the Relay compiler:\n     ```bash\n     pnpm run relay\n     ```\n\n### Running with Tmux/ Tmuxinator\n\n```bash\nsudo apt-get install tmux tmuxinator\n\ntmuxinator start qa_forum\n```\n\n### Development Workflow\n\n- **Starting the Backend**: Use `pdm dev` to start the FastAPI server.\n- **Generating GraphQL Schema**: Run `pdm run generate-graphql-schema` to update the schema in the backend.\n- **Starting the Frontend**: Use `pnpm run dev` in the `client` directory to start the Vite development server, and `pnpm run relay` to run the Relay compiler.\n\n## Usage\n\nOnce the servers are running, open your browser to the frontend's URL to access the QA Forum. Users can ask questions and submit answers, and vote on questions anonymously.\n\nYou can visit the site at https://localhost when deploying with Docker Compose\n\n## Contributing\n\nContributions are welcome! Please submit a pull request or file an issue if you encounter bugs or have feature suggestions.\n\n## License\n\nSee the project license [here](./LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faryaniyaps%2Fqa-forum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faryaniyaps%2Fqa-forum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faryaniyaps%2Fqa-forum/lists"}