Skip to content
Merged
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
25 changes: 25 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Summary

Describe the problem and the outcome of this change.

## Impact

- Users/operators affected:
- Data or schema impact:
- Configuration impact:
- Compatibility impact:

## Validation

- [ ] Relevant automated tests pass
- [ ] Formatting and lint checks pass
- [ ] Dependency and secret scans pass
- [ ] Failure paths were exercised
- [ ] Migration/rollback notes are included when applicable

## Security and operations

- Authentication/authorization impact:
- Personal or payment data impact:
- Logging/metrics/alerting impact:
- Rollout and rollback plan:
18 changes: 9 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ jobs:
- name: Start frontend for browser smoke
working-directory: AutoPartsStore/Frontend/client
run: |
npm run dev -- --host 127.0.0.1 --port 4173 --strictPort > "$RUNNER_TEMP/parca-vite.log" 2>&1 &
echo $! > "$RUNNER_TEMP/parca-vite.pid"
npm run dev -- --host 127.0.0.1 --port 4173 --strictPort > "$RUNNER_TEMP/fitmentops-vite.log" 2>&1 &
echo $! > "$RUNNER_TEMP/fitmentops-vite.pid"
for attempt in {1..40}; do
if curl --fail --silent http://127.0.0.1:4173/ >/dev/null; then
exit 0
fi
sleep 0.25
done
cat "$RUNNER_TEMP/parca-vite.log"
cat "$RUNNER_TEMP/fitmentops-vite.log"
exit 1

- name: Run checkout browser smoke
Expand All @@ -75,8 +75,8 @@ jobs:
- name: Stop frontend browser-smoke server
if: always()
run: |
if [ -f "$RUNNER_TEMP/parca-vite.pid" ]; then
kill "$(cat "$RUNNER_TEMP/parca-vite.pid")" 2>/dev/null || true
if [ -f "$RUNNER_TEMP/fitmentops-vite.pid" ]; then
kill "$(cat "$RUNNER_TEMP/fitmentops-vite.pid")" 2>/dev/null || true
fi

- name: Set up .NET
Expand All @@ -88,16 +88,16 @@ jobs:
run: dotnet tool restore

- name: Restore backend
run: dotnet restore "parca muh.sln"
run: dotnet restore FitmentOps.sln

- name: Audit NuGet dependencies
run: dotnet list "parca muh.sln" package --vulnerable --include-transitive
run: dotnet list FitmentOps.sln package --vulnerable --include-transitive

- name: Build backend with warnings as errors
run: dotnet build "parca muh.sln" --configuration Release --no-restore -warnaserror
run: dotnet build FitmentOps.sln --configuration Release --no-restore -warnaserror

- name: Verify backend formatting
run: dotnet format "parca muh.sln" --verify-no-changes --no-restore
run: dotnet format FitmentOps.sln --verify-no-changes --no-restore

- name: Verify EF Core migration snapshot
env:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,23 @@ public void Sign_RejectsMissingCredentials(string? apiKey, string? secretKey)
Encoding.UTF8.GetBytes(ExactBody),
RandomKey));
}

[Theory]
[InlineData("payment/initialize")]
[InlineData("//sandbox-api.iyzipay.com/payment/initialize")]
[InlineData("https://sandbox-api.iyzipay.com/payment/initialize")]
[InlineData("/payment/initialize?conversationId=order-42")]
[InlineData("/payment/initialize#fragment")]
[InlineData("/payment\\initialize")]
[InlineData("/payment initialize")]
[InlineData("/payment\ninitialize")]
public void Sign_RejectsUnsafeOrNonPathSigningInputs(string uriPath)
{
Assert.Throws<ArgumentException>(() => IyzicoRequestSigner.Sign(
ApiKey,
SecretKey,
uriPath,
Encoding.UTF8.GetBytes(ExactBody),
RandomKey));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,11 @@ private static void ValidateUriPath(string uriPath)
ArgumentException.ThrowIfNullOrWhiteSpace(uriPath);

if (!uriPath.StartsWith("/", StringComparison.Ordinal) ||
uriPath.StartsWith("//", StringComparison.Ordinal) ||
uriPath.Contains('?') ||
uriPath.Contains('#') ||
Uri.TryCreate(uriPath, UriKind.Absolute, out _))
uriPath.Contains('\\') ||
uriPath.Any(character => char.IsWhiteSpace(character) || char.IsControl(character)))
{
throw new ArgumentException(
"The iyzico signing input must be an absolute URI path without query or fragment.",
Expand Down
4 changes: 2 additions & 2 deletions AutoPartsStore/Backend/AutoPartsStore.API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"] ?? "AutoPartsStore",
ValidAudience = builder.Configuration["Jwt:Audience"] ?? "AutoPartsStoreUsers",
ValidIssuer = builder.Configuration["Jwt:Issuer"] ?? "FitmentOps.API",
ValidAudience = builder.Configuration["Jwt:Audience"] ?? "FitmentOps.Web",
Comment on lines +122 to +123

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep JWT issuer/audience fallbacks aligned

When a deployment or test host provides only Jwt:Key and relies on the issuer/audience fallbacks, the bearer middleware now validates FitmentOps.API / FitmentOps.Web, but JwtService.GenerateToken still falls back to AutoPartsStore / AutoPartsStoreUsers (Services/JwtService.cs). In that configuration, login/register can return successfully signed tokens that every protected endpoint rejects with 401, so the new defaults should be updated in both places or centralized.

Useful? React with 👍 / 👎.

IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public async Task SendOrderConfirmationEmail(Order order)
var body = $@"
<html>
<body style='font-family: Arial, sans-serif;'>
<h2>Parça Mühendisi - Sipariş Onayı</h2>
<h2>FitmentOps - Sipariş Onayı</h2>
<p>Merhaba {Encode(order.CustomerName)},</p>
<p>Siparişiniz başarıyla alınmıştır.</p>

Expand All @@ -47,7 +47,7 @@ public async Task SendOrderConfirmationEmail(Order order)
<p>Siparişinizi <a href='http://localhost:5173/siparis-takibi'>buradan</a> takip edebilirsiniz.</p>

<p>Teşekkür ederiz!</p>
<p><strong>Parça Mühendisi</strong></p>
<p><strong>FitmentOps</strong></p>
</body>
</html>
";
Expand Down Expand Up @@ -99,7 +99,7 @@ private async Task SendEmail(string toEmail, string subject, string body)
var smtpHost = _configuration["EmailSettings:SmtpServer"];
var smtpPortValue = _configuration["EmailSettings:SmtpPort"];
var fromEmail = _configuration["EmailSettings:SenderEmail"];
var senderName = _configuration["EmailSettings:SenderName"] ?? "Parça Mühendisi";
var senderName = _configuration["EmailSettings:SenderName"] ?? "FitmentOps";
var username = _configuration["EmailSettings:Username"];
var fromPassword = _configuration["EmailSettings:Password"];

Expand Down
8 changes: 4 additions & 4 deletions AutoPartsStore/Backend/AutoPartsStore.API/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
]
},
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=AutoPartsStoreDb;Trusted_Connection=true;MultipleActiveResultSets=true"
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=FitmentOpsDb;Trusted_Connection=true;MultipleActiveResultSets=true"
},
"Jwt": {
"Issuer": "AutoPartsStore",
"Audience": "AutoPartsStoreUsers"
"Issuer": "FitmentOps.API",
"Audience": "FitmentOps.Web"
},
"HostedCheckout": {
"ReservationLifetime": "00:15:00"
Expand Down Expand Up @@ -84,7 +84,7 @@
"SmtpServer": "smtp.gmail.com",
"SmtpPort": 587,
"SenderEmail": "",
"SenderName": "AutoParts Store",
"SenderName": "FitmentOps",
"AdminEmail": "",
"Username": "",
"Password": ""
Expand Down
6 changes: 6 additions & 0 deletions AutoPartsStore/Frontend/client/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@
# Development ortamı için varsayılan değer
VITE_API_BASE_URL=http://localhost:5167/api

# Optional public brand/contact settings. Blank values are hidden in the UI.
VITE_SUPPORT_EMAIL=
VITE_SUPPORT_PHONE=
VITE_BUSINESS_ADDRESS=
VITE_CAREER_EMAIL=

# Production ortamı için bu değeri değiştirin:
# VITE_API_BASE_URL=https://your-production-api.com/api
7 changes: 4 additions & 3 deletions AutoPartsStore/Frontend/client/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<!doctype html>
<html lang="en">
<html lang="tr">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="theme-color" content="#0f172a" />
<meta name="description" content="Fitment-aware automotive commerce and operations platform." />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Parça Mühendisi</title>
<title>FitmentOps</title>
</head>
<body>
<div id="root"></div>
Expand Down
4 changes: 2 additions & 2 deletions AutoPartsStore/Frontend/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion AutoPartsStore/Frontend/client/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "client",
"name": "fitmentops-web",
"private": true,
"version": "0.0.0",
"type": "module",
Expand Down
9 changes: 5 additions & 4 deletions AutoPartsStore/Frontend/client/src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link } from 'react-router';
import { brand } from '../config/brand';

const Footer = () => {
return (
Expand Down Expand Up @@ -35,15 +36,15 @@ const Footer = () => {
<div className="footer-section">
<h3>İletişim</h3>
<ul>
<li>Telefon: 0850 123 45 67</li>
<li>Email: info@parcamuhendisi.com</li>
<li>Adres: İstanbul, Türkiye</li>
<li><Link to="/iletisim">Destek merkezi</Link></li>
{brand.supportEmail && <li>E-posta: {brand.supportEmail}</li>}
<li>Operasyon bölgesi: Türkiye</li>
</ul>
</div>
</div>

<div className="footer-bottom">
<p>&copy; 2025 Parça Mühendisi. Tüm hakları saklıdır.</p>
<p>&copy; {new Date().getFullYear()} {brand.name}. Apache-2.0 lisansı ile sunulur.</p>
</div>
</div>
</footer>
Expand Down
5 changes: 3 additions & 2 deletions AutoPartsStore/Frontend/client/src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useCart } from '../context/CartContext';
import { useAuth } from '../context/AuthContext';
import { useWishlist } from '../context/WishlistContext';
import { categoriesAPI, brandsAPI } from '../services/api';
import { brand } from '../config/brand';

const Header = () => {
const { getCartCount } = useCart();
Expand Down Expand Up @@ -64,7 +65,7 @@ const Header = () => {
<header className="header">
<div className="header-top">
<div className="container">
<div>Hoş Geldiniz! Yedek Parça Mühendisi</div>
<div>{brand.tagline}</div>
<div>
{user ? (
<div className="user-menu-container">
Expand Down Expand Up @@ -111,7 +112,7 @@ const Header = () => {
<div className="header-main">
<div className="container">
<Link to="/" className="logo">
Parça Mühendisi
{brand.name}
</Link>

<div className="search-bar">
Expand Down
2 changes: 1 addition & 1 deletion AutoPartsStore/Frontend/client/src/components/SeoHead.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const SeoHead = ({ title, description, canonicalPath, structuredData }) => {
if (structuredData) {
script = document.createElement('script');
script.type = 'application/ld+json';
script.dataset.parcaMuhendisiSeo = 'true';
script.dataset.fitmentOpsSeo = 'true';
script.textContent = JSON.stringify(structuredData).replace(/</g, '\\u003c');
document.head.appendChild(script);
}
Expand Down
10 changes: 10 additions & 0 deletions AutoPartsStore/Frontend/client/src/config/brand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const optionalSetting = (value) => (typeof value === 'string' ? value.trim() : '');

export const brand = Object.freeze({
name: 'FitmentOps',
tagline: 'Automotive Commerce & Operations Platform',
supportEmail: optionalSetting(import.meta.env.VITE_SUPPORT_EMAIL),
supportPhone: optionalSetting(import.meta.env.VITE_SUPPORT_PHONE),
businessAddress: optionalSetting(import.meta.env.VITE_BUSINESS_ADDRESS),
careerEmail: optionalSetting(import.meta.env.VITE_CAREER_EMAIL),
});
56 changes: 23 additions & 33 deletions AutoPartsStore/Frontend/client/src/pages/AboutPage.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { brand } from '../config/brand';
import './InfoPages.css';

const AboutPage = () => {
Expand All @@ -8,55 +9,44 @@ const AboutPage = () => {

<div className="info-content">
<section className="info-section">
<h2>Parça Mühendisi - Otomotiv Yedek Parça Uzmanı</h2>
<h2>{brand.name}: doğru parçadan kontrollü operasyona</h2>
<p>
Parça Mühendisi olarak, 2010 yılından bu yana otomotiv sektöründe
kaliteli ve güvenilir yedek parça tedariki konusunda müşterilerimize
hizmet vermekteyiz. Türkiye'nin dört bir yanına ulaşan geniş ürün
yelpazemiz ve uzman ekibimizle, aracınız için ihtiyaç duyduğunuz
her türlü yedek parçayı en uygun fiyatlarla sunuyoruz.
{brand.name}, otomotiv satış sonrası pazarı için araç uyumluluğu,
ürün kataloğu, sipariş, ödeme, sevkiyat, iade ve B2B süreçlerini
tek bir operasyon platformunda birleştiren açık kaynaklı bir
yazılım projesidir.
</p>
</section>

<section className="info-section">
<h2>Vizyonumuz</h2>
<h2>Ürün yaklaşımı</h2>
<p>
Türkiye'nin en güvenilir ve tercih edilen otomotiv yedek parça
tedarikçisi olmak, müşterilerimize en kaliteli ürünleri en hızlı
şekilde ulaştırmak vizyonumuzun temelini oluşturmaktadır.
Platform, doğrulanmamış bir parçayı uyumlu göstermemeyi ve harici
sağlayıcı yapılandırılmadığında işlemleri güvenli biçimde kapalı
tutmayı temel alır. Fitment sonucu; katalog kanıtı, kaynak ve güven
seviyesiyle birlikte değerlendirilir.
</p>
</section>

<section className="info-section">
<h2>Misyonumuz</h2>
<p>
Müşteri memnuniyetini ön planda tutarak, orijinal ve kaliteli
yedek parçaları uygun fiyatlarla sunmak, hızlı ve güvenilir
teslimat ile müşterilerimizin ihtiyaçlarını en iyi şekilde
karşılamaktır.
</p>
</section>

<section className="info-section">
<h2>Neden Biz?</h2>
<h2>Platform kapsamı</h2>
<ul className="feature-list">
<li>✓ 15 yıllık sektör tecrübesi</li>
<li>✓ 10.000+ orijinal ve muadil yedek parça çeşidi</li>
<li>✓ Türkiye geneline hızlı kargo</li>
<li>✓ Uzman müşteri hizmetleri ekibi</li>
<li>✓ Güvenli ödeme seçenekleri</li>
<li>✓ Kolay iade ve değişim</li>
<li>✓ Uygun fiyat garantisi</li>
<li>✓ Araç ağacı, OEM ve interchange kodlarıyla kanıtlı uyumluluk</li>
<li>✓ Sunucu fiyatlı, idempotent checkout ve stok rezervasyonu</li>
<li>✓ Ödeme, iade, sevkiyat ve RMA durum makineleri</li>
<li>✓ Garaj, bakım günlüğü ve parça geçmişi</li>
<li>✓ B2B fiyatlandırma, RFQ, tedarikçi ve satış kanalı yönetimi</li>
<li>✓ Rol ayrımlı yönetim, audit zinciri ve operasyonel sağlık</li>
</ul>
</section>

<section className="info-section">
<h2>Ürün Kalitemiz</h2>
<h2>Yayın durumu</h2>
<p>
Tüm ürünlerimiz, uluslararası kalite standartlarına uygun olarak
seçilmekte ve test edilmektedir. Orijinal parça üreticileri ve
güvenilir tedarikçilerle çalışarak, müşterilerimize en kaliteli
ürünleri sunmayı garanti ediyoruz.
Bu sürüm üretim öncesi mühendislik aşamasındadır. Gerçek ödeme,
e-belge, kargo ve pazaryeri işlemleri; sağlayıcı sözleşmeleri,
hukuki kontroller ve sandbox sertifikasyonu tamamlanmadan
etkinleştirilmez.
</p>
</section>
</div>
Expand Down
Loading
Loading