Upgrade Gradle 7.4 → 8.12 with compatible plugin versions#624
Upgrade Gradle 7.4 → 8.12 with compatible plugin versions#624devin-ai-integration[bot] wants to merge 7 commits into
Conversation
- Added a simple note confirming RealWorld API spec compliance - This is a test change to verify PR workflow functionality Co-Authored-By: Gardner Johnson <gardnerjohnson@gmail.com>
…st-dummy-change Test: Add testing verification note to README
- Modern React 18 frontend with TypeScript and Tailwind CSS - Complete RealWorld specification implementation - User authentication with JWT token management - Article management (create, view, edit, delete) - Article feed with pagination - User profiles and following functionality - Comments system for articles - Social features (favorites, following) - Tag-based article categorization - Responsive design with modern UI - Full API integration with Spring Boot backend - Development server on localhost:3000 - Production build support Features implemented: - User registration and login - Article creation and editing with markdown support - Global article feed - User profiles and social following - Comment system - Article favoriting - Tag filtering - JWT authentication integration - Error handling and validation - Modern responsive UI design The frontend successfully demonstrates all backend API functionality through a visual web interface, replacing raw JSON responses with a complete social blogging platform user experience. Co-Authored-By: Gardner Johnson <gardnerjohnson@gmail.com>
- Remove node_modules from git tracking and add to .gitignore - Configure environment variables for API base URL using VITE_API_BASE_URL - Add TypeScript definitions for Vite environment variables - Remove unused 'User' import to fix TypeScript error Addresses the 5 critical issues identified in PR review: 1. ✅ Remove node_modules from git (added to .gitignore) 2. 🔄 Test complete user journey (next step) 3. ✅ Configure environment variables (VITE_API_BASE_URL) 4. 🔄 Verify CORS configuration (next step) 5. 🔄 Test authentication flow thoroughly (next step) Co-Authored-By: Gardner Johnson <gardnerjohnson@gmail.com>
…d-react-frontend Add React Frontend Application for RealWorld API
- Update Gradle wrapper from 7.4 to 8.12 - Update Spotless plugin from 6.2.1 to 6.25.0 - Update DGS codegen plugin from 5.0.6 to 6.3.0 - Update Spring Boot plugin from 2.6.3 to 2.7.18 (minimum for Gradle 8) - Update dependency-management plugin from 1.0.11.RELEASE to 1.1.6 - Update DGS runtime from 4.9.21 to 5.5.1 (required by codegen 6.3.0) - Add DGS platform BOM and pin graphql-java 19.2 to prevent Spring Boot BOM from downgrading it to 18.x - Adapt PageInfo usage in ArticleDatafetcher and CommentDatafetcher to use the DGS-generated PageInfo type instead of graphql.relay.DefaultPageInfo All 68 tests pass.
🤖 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:
|
| const articleData: NewArticle = { | ||
| title, | ||
| description, | ||
| body, | ||
| tagList, | ||
| }; | ||
|
|
||
| try { | ||
| let article; | ||
| if (isEditing && slug) { | ||
| article = await articlesApi.updateArticle(slug, articleData); |
There was a problem hiding this comment.
🟡 Article updates always send tagList to an endpoint that cannot accept it
The editor reuses the create-article payload for updates, so editing an article sends { article: { title, description, body, tagList } } to PUT /articles/{slug}. The backend's update DTO only supports title, body, and description (src/main/java/io/spring/application/article/UpdateArticleParam.java:12-15), and this app has Jackson root unwrapping enabled (src/main/resources/application.properties:5), so the extra tagList property can be rejected as an unrecognized field before the update executes. Creating articles needs tagList, but updating should omit it unless the backend contract is extended.
Prompt for agents
Fix the ArticleEditor update path so it matches the existing REST contract. In frontend/src/pages/ArticleEditor.tsx, create requests may include tagList, but update requests should only send the fields accepted by src/main/java/io/spring/application/article/UpdateArticleParam.java: title, description, and body. Alternatively, if editing tags is intended, update the backend UpdateArticleParam and article update logic to support tagList consistently.
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| } catch (err: any) { | ||
| setError(err.response?.data?.errors?.['email or password'] || 'Login failed'); | ||
| } finally { |
There was a problem hiding this comment.
🟡 Login errors ignore the backend's authentication message
Failed logins from the existing backend return { "message": "invalid email or password" } (src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java:50-59 and src/test/java/io/spring/api/UsersApiTest.java:261-269), but this catch block only looks for errors['email or password']. As a result, normal bad-password/bad-email responses always show the generic Login failed text instead of the actual backend error, making authentication failures look like an unexpected client failure.
| } catch (err: any) { | |
| setError(err.response?.data?.errors?.['email or password'] || 'Login failed'); | |
| } finally { | |
| } catch (err: any) { | |
| setError(err.response?.data?.message || err.response?.data?.errors?.['email or password'] || 'Login failed'); | |
| } finally { |
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| const handleArticleUpdate = (updatedArticle: Article) => { | ||
| setArticles(articles.map(article => | ||
| article.slug === updatedArticle.slug ? updatedArticle : article | ||
| )); | ||
| }; |
There was a problem hiding this comment.
🟡 Stale closure in handleArticleUpdate overwrites concurrent favorite updates
In Home.tsx, handleArticleUpdate captures the articles array from its render closure and passes it as the onUpdate prop to ArticleCard. When a user rapidly favorites two different articles (A then B), B's handleFavorite (in ArticleCard) holds a reference to the onUpdate from before A's state update. When B's API response arrives, it calls setArticles(articles.map(...)) using the stale pre-A-update articles array, overwriting A's favorite status update. The same pattern exists in frontend/src/pages/Profile.tsx:71-75. The fix is to use React's functional updater: setArticles(prev => prev.map(...)), which always operates on the latest state.
| const handleArticleUpdate = (updatedArticle: Article) => { | |
| setArticles(articles.map(article => | |
| article.slug === updatedArticle.slug ? updatedArticle : article | |
| )); | |
| }; | |
| const handleArticleUpdate = (updatedArticle: Article) => { | |
| setArticles(prev => prev.map(article => | |
| article.slug === updatedArticle.slug ? updatedArticle : article | |
| )); | |
| }; |
Was this helpful? React with 👍 or 👎 to provide feedback.
| const handleArticleUpdate = (updatedArticle: Article) => { | ||
| setArticles(articles.map(article => | ||
| article.slug === updatedArticle.slug ? updatedArticle : article | ||
| )); | ||
| }; |
There was a problem hiding this comment.
🟡 Stale closure in Profile's handleArticleUpdate overwrites concurrent favorite updates
Same stale closure issue as in Home.tsx. handleArticleUpdate in Profile.tsx captures the articles array from the render closure. When the user rapidly favorites multiple articles on a profile page, subsequent API responses use stale state, potentially reverting earlier favorite status updates in the UI.
| const handleArticleUpdate = (updatedArticle: Article) => { | |
| setArticles(articles.map(article => | |
| article.slug === updatedArticle.slug ? updatedArticle : article | |
| )); | |
| }; | |
| const handleArticleUpdate = (updatedArticle: Article) => { | |
| setArticles(prev => prev.map(article => | |
| article.slug === updatedArticle.slug ? updatedArticle : article | |
| )); | |
| }; |
Was this helpful? React with 👍 or 👎 to provide feedback.
| const updateUser = async (userData: Partial<UserWithToken>) => { | ||
| const updatedUser = await authApi.updateUser(userData); | ||
| setUser(updatedUser); | ||
| }; |
There was a problem hiding this comment.
🟡 updateUser doesn't persist token to localStorage, breaking pattern used by login/register
In useAuth.ts, the updateUser function (line 63-66) calls setUser(updatedUser) but does NOT call localStorage.setItem('token', updatedUser.token), unlike login (line 48) and register (line 54) which both persist the token. The API interceptor (frontend/src/services/api.ts:24) reads the token from localStorage, not from the user state. While the current backend echoes the same token on update (src/main/java/io/spring/api/CurrentUserApi.java:48), this inconsistency means that if the backend is ever changed to issue a refreshed token on profile update, subsequent API calls would use the stale token from localStorage, causing authentication failures.
| const updateUser = async (userData: Partial<UserWithToken>) => { | |
| const updatedUser = await authApi.updateUser(userData); | |
| setUser(updatedUser); | |
| }; | |
| const updateUser = async (userData: Partial<UserWithToken>) => { | |
| const updatedUser = await authApi.updateUser(userData); | |
| localStorage.setItem('token', updatedUser.token); | |
| setUser(updatedUser); | |
| }; |
Was this helpful? React with 👍 or 👎 to provide feedback.
|
The Devin Review findings are on |
Summary
Upgrades Gradle from 7.4 to 8.12, along with the minimum plugin/dependency bumps required for Gradle 8 compatibility.
Changes
Code changes
ArticleDatafetcher.javaandCommentDatafetcher.javawere updated to use the DGS-generatedio.spring.graphql.types.PageInfotype instead ofgraphql.relay.DefaultPageInfo, which is required by the DGS codegen 6.3.0 generated builder API.Files modified
gradle/wrapper/gradle-wrapper.properties— wrapper versiongradle/wrapper/gradle-wrapper.jar— updated wrapper jargradlew— updated wrapper scriptbuild.gradle— plugin and dependency versionssrc/main/java/io/spring/graphql/ArticleDatafetcher.java— PageInfo type adaptationsrc/main/java/io/spring/graphql/CommentDatafetcher.java— PageInfo type adaptationReview & Testing Checklist for Human
./gradlew clean test) — confirmed passing locallypageInfofields (thePageInfotype was changed from relay to DGS-generated)./gradlew bootRun) and smoke-test REST and GraphQL endpointsNotes
The original plan was to only bump the Gradle wrapper, Spotless plugin, and DGS codegen plugin. However, Gradle 8 removed internal APIs used by Spring Boot 2.6.x and the older dependency-management plugin, requiring those to be bumped as well. The DGS codegen 6.3.0 generates code targeting DGS 5.x APIs, so the DGS runtime also needed upgrading. Spring Boot 2.7.18's BOM manages graphql-java at 18.x, conflicting with DGS 5.5.1's requirement for 19.2, so graphql-java is explicitly pinned.
Link to Devin session: https://app.devin.ai/sessions/f557987295354b4b8aceaf67b3506b28
Requested by: @eddiemattout-cog
Devin Review
a031393