feat: Add Angular 21 srushtiUI with .NET Core 8.0 API backend integration#69
feat: Add Angular 21 srushtiUI with .NET Core 8.0 API backend integration#69devin-ai-integration[bot] wants to merge 12 commits intomainfrom
Conversation
… sales and purchase management - Login with admin/normal user roles - Admin-only user registration page - Dashboard with business overview - Sales module: Add Sales Data, List Sales Data, Sales Report - Purchase module: Purchase Entry, Purchase List, Purchase Report - Role-based sidebar navigation - Responsive layout with collapsible sidebar
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
… utility - Add SalesReturn and PurchaseReturn models - Add SalesReturnService and PurchaseReturnService with CRUD and report generation - Create shared report.utils.ts to eliminate duplicate aggregation logic - Refactor SalesService and PurchaseService to use shared utility - Add Sales Return components (Add, List, Report) with red color scheme - Add Purchase Return components (Add, List, Report) with orange color scheme - Update app.routes.ts with sales-return and purchase-return routes - Update sidebar navigation with Sales Return and Purchase Return sections - Update Dashboard with return statistics cards - Update existing report components to use shared utility types
…) with Cypress Unit Tests (Jasmine/Karma - 93 specs): - Services: AuthService, SalesService, PurchaseService, SalesReturnService, PurchaseReturnService, StockService - Guards: authGuard, adminGuard - Utilities: buildCategoryBreakdown, buildPaymentMethodBreakdown - Components: LoginComponent, DashboardComponent, AddStockComponent, StockListComponent, StockReportComponent E2E Tests (Cypress - 36 specs): - Login flow: form display, validation, admin/user login, logout - Navigation: role-based access, sidebar, footer, sidebar toggle - Sales & Purchase: navigation, form submission, list/report pages - Returns: sales return and purchase return CRUD flows - Stock: add stock, validation, list/report navigation
…anged menu - Employee module (blue #1565c0): Add Employee, Employee List, Employee Report - Payroll module (purple #6a1b9a): Add Payroll, Payroll List, Payroll Report - Auto-fill employee details in payroll form - Auto-calculate salary components (basic, HRA, DA, allowances, deductions) - Excel download for both Employee and Payroll reports - Dashboard updated with Employee and Payroll stat cards - Sidebar menu rearranged by function groups: Employee > Payroll > Sales > Purchase > Stock - Unit tests: 156 specs all passing - E2E tests: 19 new specs for Employee and Payroll flows
…lar frontend - Created SrushtiAPI with repository pattern, Dapper ORM, SQLite database - JWT authentication with Bearer token support - Controllers: Auth, Sales, Purchase, SalesReturn, PurchaseReturn, Stock, Employee, Payroll, Dashboard - Angular integration: environment configs, JWT interceptor, HttpClient provider - Updated all 8 Angular services to use HTTP calls instead of in-memory data - Updated list components to re-fetch data after deletion - Fixed add-payroll component for async employee lookup - All data now persists in SQLite database via API
| public async Task<IActionResult> Login([FromBody] LoginDto loginDto) | ||
| { | ||
| var user = await _userRepository.GetByUsernameAsync(loginDto.Username); | ||
| if (user == null || user.Password != loginDto.Password) |
There was a problem hiding this comment.
🔴 Plaintext password storage and comparison enables credential theft
Passwords are stored in plaintext in the SQLite database (DatabaseInitializer.cs:145-147) and compared directly via user.Password != loginDto.Password. If the database file is ever accessed (it's a local .db file), all credentials are immediately exposed. The Register endpoint at SrushtiAPI/Controllers/AuthController.cs:55 also stores the password in plaintext. Passwords should be hashed (e.g., with bcrypt) before storage and compared using a constant-time hash comparison.
Was this helpful? React with 👍 or 👎 to provide feedback.
| [HttpPost("register")] | ||
| public async Task<IActionResult> Register([FromBody] RegisterDto registerDto) | ||
| { | ||
| if (await _userRepository.UsernameExistsAsync(registerDto.Username)) | ||
| return BadRequest(new { message = "Username already exists" }); | ||
|
|
||
| var user = new User | ||
| { | ||
| Username = registerDto.Username, | ||
| Password = registerDto.Password, | ||
| Role = registerDto.Role, | ||
| FullName = registerDto.FullName, | ||
| Email = registerDto.Email | ||
| }; | ||
|
|
||
| var id = await _userRepository.AddAsync(user); | ||
| return CreatedAtAction(nameof(Login), new { id }, new { message = "User registered successfully", id }); | ||
| } |
There was a problem hiding this comment.
🔴 Unauthenticated Register endpoint allows privilege escalation to admin
The Register endpoint has no [Authorize] attribute and directly accepts the Role field from the client via RegisterDto.Role (SrushtiAPI/DTOs/LoginDto.cs:21). Any anonymous user can POST to /api/auth/register with {"role": "admin", ...} to create an admin account, granting themselves full administrative privileges. The role should either be forced to "user" server-side, or the endpoint should require admin authorization.
Prompt for agents
In SrushtiAPI/Controllers/AuthController.cs, the Register method at lines 46-63 should either: (1) Force the role to 'user' by ignoring registerDto.Role and hardcoding user.Role = "user", or (2) Add [Authorize(Roles = "admin")] to the Register method so only admins can create new users with arbitrary roles. Option 1 is simpler; Option 2 aligns with the admin-only user registration in the frontend.
Was this helpful? React with 👍 or 👎 to provide feedback.
| [HttpGet("users")] | ||
| public async Task<IActionResult> GetUsers() | ||
| { | ||
| var users = await _userRepository.GetAllAsync(); | ||
| return Ok(users.Select(u => new { u.Id, u.Username, u.FullName, u.Email, u.Role })); | ||
| } |
There was a problem hiding this comment.
🔴 GetUsers endpoint exposes user data without authentication
The GetUsers method at line 39 has no [Authorize] attribute, and the AuthController class itself also lacks one. This means any unauthenticated user can call GET /api/auth/users to retrieve the full list of users (IDs, usernames, full names, emails, and roles). All other data controllers (DashboardController, EmployeeController, etc.) use [Authorize] at the class level, making this an inconsistency.
| [HttpGet("users")] | |
| public async Task<IActionResult> GetUsers() | |
| { | |
| var users = await _userRepository.GetAllAsync(); | |
| return Ok(users.Select(u => new { u.Id, u.Username, u.FullName, u.Email, u.Role })); | |
| } | |
| [HttpGet("users")] | |
| [Authorize] | |
| public async Task<IActionResult> GetUsers() |
Was this helpful? React with 👍 or 👎 to provide feedback.
| @@ -0,0 +1,4 @@ | |||
| export const environment = { | |||
| production: false, | |||
| apiBaseUrl: 'http://localhost:5000/api' | |||
There was a problem hiding this comment.
🔴 Frontend API base URL port mismatch prevents backend communication
The Angular frontend's environment.ts configures apiBaseUrl as http://localhost:5000/api, but the .NET API's launchSettings.json:17 configures the application to run on http://localhost:5128. This port mismatch means all API calls from the frontend will fail with connection refused errors, making the application non-functional in development.
| apiBaseUrl: 'http://localhost:5000/api' | |
| apiBaseUrl: 'http://localhost:5128/api' |
Was this helpful? React with 👍 or 👎 to provide feedback.
| } | ||
|
|
||
| onSubmit(): void { | ||
| if (!this.stockEntry.productName || !this.stockEntry.category || this.stockEntry.quantity < 0 || |
There was a problem hiding this comment.
🟡 Stock add form validation allows quantity of 0 to be submitted
In add-stock.component.ts:44, the quantity validation uses this.stockEntry.quantity < 0 which allows a stock entry with quantity 0 to pass validation and be submitted. All other add-form components (sales, purchase, returns) use the !this.stockEntry.quantity falsy check which correctly blocks 0. A stock entry with 0 quantity is semantically invalid and would result in a totalValue of 0.
| if (!this.stockEntry.productName || !this.stockEntry.category || this.stockEntry.quantity < 0 || | |
| if (!this.stockEntry.productName || !this.stockEntry.category || !this.stockEntry.quantity || |
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Adds a new Angular 21 standalone project (
srushtiUI/) implementing a Sales & Purchase Management System with Employee Management and Payroll, backed by a .NET Core 8.0 Web API (SrushtiAPI/) using Dapper ORM and SQLite for persistent data storage. Role-based access control via JWT authentication.Frontend Features (Angular 21):
admin/admin123,user/user123)adminGuard)Backend Features (.NET Core 8.0 Web API):
IRepository<T>base interfaceIntegration Architecture:
environment.ts,environment.prod.ts) with configurable API base URLjwt.interceptor.ts) auto-attaches Bearer tokens to all HTTP requests, handles 401 redirects with race-condition protection viaAuthService.logout()and a redirect debounce flagBehaviorSubjecttoHttpClientHTTP callsCode quality: Shared report utilities (
report.utils.ts,excel-export.utils.ts),.gitignorefor API build artifacts.Updates since last revision
Authorizationheader — triggering an infinite redirect loop (auth guard still sawcurrentUserSubjectas truthy because onlylocalStoragewas cleared, not the BehaviorSubject). Fix:authService.logout()(which clears bothlocalStorageandcurrentUserSubject) instead of manually removing localStorage itemsisRedirectingmodule-level flag to debounce — only the first 401 triggers the logout + redirect; subsequent 401s from parallel requests are silently caught/auth/loginrequests from the 401 handler to avoid clearing credentials during login failuressubscribe()calls inDashboardComponent.loadDashboardData()now use{ next, error }observer pattern with empty error handlers, preventing unhandled Observable errors from propagating when the API is unreachable or the token is being clearedReview & Testing Checklist for Human
isRedirectingflag resets correctly — The flag resets insiderouter.navigate('/login').then(...). If the navigation promise were to reject (unlikely but possible), the flag could get stuck astrue, permanently preventing 401 handling. Check edge cases like navigating while already on/loginAuthService. The.spec.tstest files may not have been updated to useHttpClientTestingModule/HttpTestingController. Runcd srushtiUI && CHROME_BIN=/path/to/chrome npx ng test --no-watch --browsers=ChromeHeadlessand expect potential failuresObservable<boolean>for delete methods, but API controllers returnOk(new { message = "..." })(an object, not a boolean). Confirm this doesn't cause runtime errors in list components after deletionappsettings.json,GET /api/auth/usersendpoint is unauthenticated. Acceptable for demo onlyRecommended test plan:
cd SrushtiAPI && dotnet run --urls "http://localhost:5000"(starts API with SQLite)cd srushtiUI && npm install && npx ng serve --port 4200(starts Angular)http://localhost:4200, log in asadmin/admin123localhost:5000/api/*withAuthorization: BearerheaderNotes
GET /api/dashboardendpoint — functional but suboptimalerror: () => {}) — errors are silently swallowed so the dashboard shows 0s rather than crashing. This means a misconfigured API will show zeros with no visible error indication to the userisRedirectingflag is module-level mutable state (not instance-scoped), which is intentional for the functional interceptor pattern but may complicate unit testingAddPayrollComponent.onEmployeeSelect()xlsxlibraryadmin/admin123(admin role),user/user123(user role) — seeded byDatabaseInitializerhttp://localhost:5000/swaggerwhen running in Development modeLink to Devin session: https://partner-workshops.devinenterprise.com/sessions/ed44f620b76842acbad64d503a469b8d