diff --git a/README.md b/README.md new file mode 100644 index 0000000..4bcee1a --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# SolarWatch + +SolarWatch is a React-based web application that allows users to track sunrise, sunset, solar noon, and day length for a given location and date. The project is built using Vite for fast development and includes features such as user authentication, protected routes, and an admin panel for managing data. + +## Features + +- **User Authentication**: Login and register functionality with JWT-based authentication. +- **Solar Data Tracking**: View sunrise, sunset, solar noon, and day length for a specific location and date. +- **Admin Panel**: Manage location and solar data (delete functionality included). +- **Responsive Design**: Styled with CSS for a clean and user-friendly interface. +- **Protected Routes**: Role-based access control for user and admin routes. +- **Docker Support**: Easily deployable using Docker. + +## Project Structure + +``` +src/ +├── App.jsx # Main application component +├── components/ # Reusable components +│ ├── AdminPanel/ # Admin panel components +│ ├── Footer/ # Footer component +│ ├── Home/ # Home page component +│ ├── Login/ # Login page component +│ ├── Navbar/ # Navigation bar component +│ ├── NotFound/ # 404 page component +│ ├── ProtectedRoute/ # Protected route logic +│ ├── Register/ # Registration page component +│ └── SolarWatch/ # Solar data tracking component +├── assets/ # Static assets (e.g., SVGs) +├── App.css # Global styles +├── main.jsx # Application entry point +└── index.css # Global CSS variables and styles +``` + +## Installation + +## Docker Deployment + +1. Use Docker Compose to build: +```powershell +docker compose build +``` + +2. Then run: +```powershell +docker compose up -d +``` + +## Scripts + +- `npm run dev`: Start the development server. +- `npm run build`: Build the project for production. +- `npm run preview`: Preview the production build. +- `npm run lint`: Run ESLint to check for code issues. + +## Technologies Used + +- **Frontend**: React, React Router, Vite +- **Styling**: CSS +- **State Management**: React Hooks +- **Authentication**: JWT +- **Deployment**: Docker, Nginx + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- SVG icons are sourced from [svgrepo.com](https://www.svgrepo.com/). +- This is a project for me to showcase my full stack skills. \ No newline at end of file diff --git a/SolarWatch/Program.cs b/SolarWatch/Program.cs index 49f1e3a..283f853 100644 --- a/SolarWatch/Program.cs +++ b/SolarWatch/Program.cs @@ -21,7 +21,7 @@ public static void Main(string[] args) // for appSettings.json var configuration = builder.Configuration; // this here in program.cs to ready out connection string - builder.Services.Configure(builder.Configuration.GetSection("AppSettings")); // this so it can be used anywhere NOT in program.cs + builder.Services.Configure(configuration.GetSection("AppSettings")); // this so it can be used anywhere NOT in program.cs // Add services to the container. builder.Services.AddControllers(); @@ -101,19 +101,15 @@ public static void Main(string[] args) }; }); - - - if (!builder.Environment.IsEnvironment("Test")) - { builder.Services.AddDbContext(options => { + var connectionString = Environment.GetEnvironmentVariable("CONNECTION_STRING_DOCKER") ?? configuration["AppSettings:CONNECTION_STRING_DOCKER"]; options.UseNpgsql(connectionString); - }); - } + }); builder.Services .AddIdentityCore(options => @@ -136,6 +132,7 @@ public static void Main(string[] args) // register appsettings var config = scope.ServiceProvider.GetRequiredService>().Value; + /* // do automatic migraions var services = scope.ServiceProvider; try @@ -148,6 +145,7 @@ public static void Main(string[] args) var logger = services.GetRequiredService>(); logger.LogError(ex, "An error occurred while applying database migrations."); } + */ // run role seeder var authenticationSeeder = scope.ServiceProvider.GetRequiredService(); diff --git a/SolarWatch/README.md b/SolarWatch/README.md index 645f5dc..c57a9ea 100644 --- a/SolarWatch/README.md +++ b/SolarWatch/README.md @@ -1 +1,37 @@ -No starter code is provided. Start from scratch! \ No newline at end of file +# SolarWatch + +SolarWatch is a .NET 9.0 web application that provides solar data and geocoding information. The application includes authentication and authorization features using JWT tokens. + +## Features + +- User registration and login +- Fetch geocoding data for a given location +- Fetch solar data for a given location and date +- Integration tests for authentication and data fetching + +## Technologies Used + +- .NET 9.0 +- ASP.NET Core +- Entity Framework Core +- In-memory database for testing +- JWT Authentication +- FluentAssertions for testing +- xUnit for unit testing + +## Getting Started + +### Prerequisites + +- .NET 9.0 SDK +- Visual Studio 2022 + + +## Project Structure + +- `SolarWatch/`: The main application project. +- `SolarWatchTests/`: The test project containing integration tests. + +## Configuration + +The application uses an in-memory database for testing purposes. You can configure the database and other settings in the `appsettings.json` file. diff --git a/SolarWatch/Services/Authentication/AuthenticationSeeder.cs b/SolarWatch/Services/Authentication/AuthenticationSeeder.cs index 6cf3813..f5e9479 100644 --- a/SolarWatch/Services/Authentication/AuthenticationSeeder.cs +++ b/SolarWatch/Services/Authentication/AuthenticationSeeder.cs @@ -7,11 +7,13 @@ public class AuthenticationSeeder private RoleManager roleManager; private UserManager userManager; + private readonly IConfiguration iConfiguration; - public AuthenticationSeeder(RoleManager roleManager, UserManager userManager) + public AuthenticationSeeder(RoleManager roleManager, UserManager userManager, IConfiguration iConfiguration) { this.roleManager = roleManager; this.userManager = userManager; + this.iConfiguration = iConfiguration; } public void AddRoles() @@ -45,7 +47,7 @@ private async Task CreateAdminIfNotExists() if (adminInDb == null) { var admin = new IdentityUser { UserName = "admin", Email = "admin@admin.com" }; - var adminCreated = await userManager.CreateAsync(admin, "admin123!420"); + var adminCreated = await userManager.CreateAsync(admin, iConfiguration["AppSettings:ADMIN_PASSWORD"]); if (adminCreated.Succeeded) { diff --git a/SolarWatch/SolarWatch.csproj b/SolarWatch/SolarWatch.csproj index 113dcec..f8213f1 100644 --- a/SolarWatch/SolarWatch.csproj +++ b/SolarWatch/SolarWatch.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable 65b42dee-4409-492b-9f67-eb2d386eaf15 @@ -10,17 +10,17 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + - + diff --git a/SolarWatch/SolarWatch.sln.DotSettings.user b/SolarWatch/SolarWatch.sln.DotSettings.user index 76bda7d..ce7a076 100644 --- a/SolarWatch/SolarWatch.sln.DotSettings.user +++ b/SolarWatch/SolarWatch.sln.DotSettings.user @@ -1,8 +1,6 @@  <SessionState ContinuousTestingMode="0" IsActive="True" Name="TestForLocations" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <TestAncestor> - <TestId>NUnit3x::799DCF59-930B-4FF3-BF8D-EDF88B1AAEB7::net9.0::SolarWatchTests.SolarWatchControllerTests</TestId> - <TestId>xUnit::799DCF59-930B-4FF3-BF8D-EDF88B1AAEB7::net9.0::SolarWatchTests.IntegrationTests</TestId> - <TestId>xUnit::799DCF59-930B-4FF3-BF8D-EDF88B1AAEB7::net9.0::SolarWatchTests.IntegrationTestForAuth</TestId> + <TestId>xUnit::799DCF59-930B-4FF3-BF8D-EDF88B1AAEB7::net8.0::SolarWatchTests.IntegrationTests</TestId> </TestAncestor> </SessionState> \ No newline at end of file diff --git a/SolarWatch/appsettings.json b/SolarWatch/appsettings.json index 3a42f2a..d60c8f8 100644 --- a/SolarWatch/appsettings.json +++ b/SolarWatch/appsettings.json @@ -8,7 +8,8 @@ "AppSettings": { "MyVariable": "Hello, World!", "CONNECTION_STRING_DOCKER": "Server=localhost;Port=29777;Password=123456;User ID=admin;Database=solar_watch", - "OpenWeatherMapApiKey": "592d954883ee58cc9e8d109051b444b5" + "OpenWeatherMapApiKey": "123456", + "ADMIN_PASSWORD": "admin123" }, "AllowedHosts": "*" } \ No newline at end of file diff --git a/SolarWatchTests/IntegrationTests.cs b/SolarWatchTests/IntegrationTests.cs index e15c533..7ed2849 100644 --- a/SolarWatchTests/IntegrationTests.cs +++ b/SolarWatchTests/IntegrationTests.cs @@ -32,16 +32,16 @@ public IntegrationTestForAuth() var webAppFactory = factory.WithWebHostBuilder(builder => { - builder.UseEnvironment("Test"); builder.ConfigureServices(services => { + /* // Remove all DbContextOptions registrations var descriptors = services.Where(d => d.ServiceType == typeof(DbContextOptions)).ToList(); foreach (var descriptor in descriptors) { services.Remove(descriptor); } - + */ // Remove any existing DbContext registration var dbContextDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(SolarWatchApiContext)); if (dbContextDescriptor != null) diff --git a/SolarWatchTests/SolarWatchTests.csproj b/SolarWatchTests/SolarWatchTests.csproj index 4bf4095..f4da15d 100644 --- a/SolarWatchTests/SolarWatchTests.csproj +++ b/SolarWatchTests/SolarWatchTests.csproj @@ -1,7 +1,7 @@ - net9.0 + net8.0 enable enable @@ -12,14 +12,15 @@ - - + + + @@ -28,6 +29,7 @@ + diff --git a/SolarWatchTests/UnitTests.cs b/SolarWatchTests/UnitTests.cs index b2ea6be..8fad3e7 100644 --- a/SolarWatchTests/UnitTests.cs +++ b/SolarWatchTests/UnitTests.cs @@ -8,6 +8,7 @@ using SolarWatch.Models; using SolarWatch.Repositories; using SolarWatch.Services; +using Assert = NUnit.Framework.Assert; namespace SolarWatchTests { diff --git a/SolarWatch_Frontend/index.html b/SolarWatch_Frontend/index.html index 04a57b4..a6ec7f2 100644 --- a/SolarWatch_Frontend/index.html +++ b/SolarWatch_Frontend/index.html @@ -1,13 +1,21 @@ - - - - - SolarWatch - - -
- - - + + + + + + + + + + + SolarWatch + + + +
+ + + + \ No newline at end of file diff --git a/SolarWatch_Frontend/public/apple-touch-icon.png b/SolarWatch_Frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..3dff64c Binary files /dev/null and b/SolarWatch_Frontend/public/apple-touch-icon.png differ diff --git a/SolarWatch_Frontend/public/favicon-96x96.png b/SolarWatch_Frontend/public/favicon-96x96.png new file mode 100644 index 0000000..1d91a33 Binary files /dev/null and b/SolarWatch_Frontend/public/favicon-96x96.png differ diff --git a/SolarWatch_Frontend/public/favicon.ico b/SolarWatch_Frontend/public/favicon.ico new file mode 100644 index 0000000..ce0713f Binary files /dev/null and b/SolarWatch_Frontend/public/favicon.ico differ diff --git a/SolarWatch_Frontend/public/favicon.svg b/SolarWatch_Frontend/public/favicon.svg new file mode 100644 index 0000000..7c86db3 --- /dev/null +++ b/SolarWatch_Frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/SolarWatch_Frontend/public/site.webmanifest b/SolarWatch_Frontend/public/site.webmanifest new file mode 100644 index 0000000..ccf313a --- /dev/null +++ b/SolarWatch_Frontend/public/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "MyWebSite", + "short_name": "MySite", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/SolarWatch_Frontend/public/vite.svg b/SolarWatch_Frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/SolarWatch_Frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/SolarWatch_Frontend/public/web-app-manifest-192x192.png b/SolarWatch_Frontend/public/web-app-manifest-192x192.png new file mode 100644 index 0000000..c25a0ba Binary files /dev/null and b/SolarWatch_Frontend/public/web-app-manifest-192x192.png differ diff --git a/SolarWatch_Frontend/public/web-app-manifest-512x512.png b/SolarWatch_Frontend/public/web-app-manifest-512x512.png new file mode 100644 index 0000000..9239d40 Binary files /dev/null and b/SolarWatch_Frontend/public/web-app-manifest-512x512.png differ diff --git a/SolarWatch_Frontend/src/App.jsx b/SolarWatch_Frontend/src/App.jsx index 082d8e6..cc9eb6e 100644 --- a/SolarWatch_Frontend/src/App.jsx +++ b/SolarWatch_Frontend/src/App.jsx @@ -12,6 +12,7 @@ import NotFound from './components/NotFound/NotFound' import Navbar from './components/Navbar/Navbar' import Footer from './components/Footer/Footer' import AdminPanel from './components/AdminPanel/AdminPanel' +import Tos from './components/Tos/Tos' function App() { @@ -22,6 +23,7 @@ function App() {
} /> + } /> } /> } /> } /> diff --git a/SolarWatch_Frontend/src/assets/noon.svg b/SolarWatch_Frontend/src/assets/noon.svg new file mode 100644 index 0000000..9a9436f --- /dev/null +++ b/SolarWatch_Frontend/src/assets/noon.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/SolarWatch_Frontend/src/assets/react.svg b/SolarWatch_Frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/SolarWatch_Frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/SolarWatch_Frontend/src/assets/sunrise.svg b/SolarWatch_Frontend/src/assets/sunrise.svg new file mode 100644 index 0000000..b51bb27 --- /dev/null +++ b/SolarWatch_Frontend/src/assets/sunrise.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/SolarWatch_Frontend/src/assets/sunset.svg b/SolarWatch_Frontend/src/assets/sunset.svg new file mode 100644 index 0000000..40fb6fd --- /dev/null +++ b/SolarWatch_Frontend/src/assets/sunset.svg @@ -0,0 +1,17 @@ + + + + + sunset 2 + Created with Sketch Beta. + + + + + + + + + + + \ No newline at end of file diff --git a/SolarWatch_Frontend/src/assets/time.svg b/SolarWatch_Frontend/src/assets/time.svg new file mode 100644 index 0000000..4b8d54a --- /dev/null +++ b/SolarWatch_Frontend/src/assets/time.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SolarWatch_Frontend/src/components/Footer/Footer.jsx b/SolarWatch_Frontend/src/components/Footer/Footer.jsx index f31939d..c2cd4c3 100644 --- a/SolarWatch_Frontend/src/components/Footer/Footer.jsx +++ b/SolarWatch_Frontend/src/components/Footer/Footer.jsx @@ -4,7 +4,7 @@ import './Footer.css' const Footer = () => { return (
- Made with ❤️ by Bence Veres + Made with ❤️ by Bence Veres
) } diff --git a/SolarWatch_Frontend/src/components/Home/Home.jsx b/SolarWatch_Frontend/src/components/Home/Home.jsx index cc72afe..5523650 100644 --- a/SolarWatch_Frontend/src/components/Home/Home.jsx +++ b/SolarWatch_Frontend/src/components/Home/Home.jsx @@ -4,7 +4,6 @@ import './Home.css' const Home = () => { - const [testMessage, setTestMessage] = useState('') const [displayOrderedList, setDisplayOrderedList] = useState(false) const displayHowTo = () => { @@ -28,6 +27,7 @@ const Home = () => { )}
+ Please read before using the site ! ) } diff --git a/SolarWatch_Frontend/src/components/Login/Login.jsx b/SolarWatch_Frontend/src/components/Login/Login.jsx index c41b2e7..095ef10 100644 --- a/SolarWatch_Frontend/src/components/Login/Login.jsx +++ b/SolarWatch_Frontend/src/components/Login/Login.jsx @@ -5,63 +5,75 @@ import './Login.css' const Login = () => { const [responseMessage, setResponseMessage] = useState(''); - + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); const navigate = useNavigate(); const handleSubmit = async (event) => { - event.preventDefault() + event.preventDefault(); + setLoading(true); + setSuccess(false); + setResponseMessage(''); + const data = new FormData(event.target) const value = Object.fromEntries(data.entries()) //console.log(JSON.stringify(value)) - const response = await fetch('/api/auth/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(value) - }) + try { + const response = await fetch('/api/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(value) + }); + + if (response.ok) { + const result = await response.json(); + localStorage.setItem('username', result.userName); + localStorage.setItem('email', result.email); + localStorage.setItem('token', result.token); + //console.log(result); + setSuccess(true); + setResponseMessage('Login successful. Redirecting to home page...'); + setTimeout(() => { + navigate('/') // Redirect to home page + }, 2000); - if (response.ok) { - const result = await response.json(); - localStorage.setItem('username', result.userName); - localStorage.setItem('email', result.email); - localStorage.setItem('token', result.token); - //console.log(result); - - setResponseMessage('Login successful. Redirecting to home page...'); - setTimeout(() => { - navigate('/') // Redirect to home page - }, 2000); - - } else { - const result = await response.json(); - const errorKey = Object.keys(result)[0]; - const errorDetails = result[errorKey][0]; - const errorMessage = `Failed to log in. ${errorKey}: ${errorDetails}`; - setResponseMessage(errorMessage); + } else { + const result = await response.json(); + const errorKey = Object.keys(result)[0]; + const errorDetails = result[errorKey][0]; + const errorMessage = `Failed to log in. ${errorKey}: ${errorDetails}`; + setResponseMessage(errorMessage); + } + } catch (e) { + setSuccess(false); + console.log(e); + } + finally { + setLoading(false); } } return (

Login

- {!responseMessage && - <> - -
- -
- -
- -
- Don't have an account? Register here - + {!success && + <> +
+ +
+ +
+ +
+ Don't have an account? Register here + }
- {responseMessage &&

{responseMessage}

} + {responseMessage &&

{responseMessage}

}
) } diff --git a/SolarWatch_Frontend/src/components/Navbar/Navbar.css b/SolarWatch_Frontend/src/components/Navbar/Navbar.css index 1286c0d..1918482 100644 --- a/SolarWatch_Frontend/src/components/Navbar/Navbar.css +++ b/SolarWatch_Frontend/src/components/Navbar/Navbar.css @@ -1,18 +1,12 @@ .navbar { display: flex; justify-content: space-between; + gap:1rem; align-items: center; background-color: #333; padding: 1rem; color: white; - border-radius: 10px; margin-bottom: 2rem; + border-bottom: solid white 2px; + overflow-x: auto; } - -.nav-user { - margin-right: 1rem; -} - -.nav-timer { - margin-left: 1rem; -} \ No newline at end of file diff --git a/SolarWatch_Frontend/src/components/Navbar/Navbar.jsx b/SolarWatch_Frontend/src/components/Navbar/Navbar.jsx index 77246a2..a06d8fc 100644 --- a/SolarWatch_Frontend/src/components/Navbar/Navbar.jsx +++ b/SolarWatch_Frontend/src/components/Navbar/Navbar.jsx @@ -33,7 +33,7 @@ const Navbar = () => { setIsAdmin(false); } }) - .catch(() => setIsAdmin(false)); + .catch(() => setIsAdmin(false)); const updateTimer = () => { const now = new Date().getTime() @@ -90,19 +90,18 @@ const Navbar = () => { return (