initial commit
This commit is contained in:
commit
a3e14cba0f
|
@ -0,0 +1,37 @@
|
||||||
|
# Full Stack Developer Teknik Ödevi
|
||||||
|
|
||||||
|
Merhaba Değerli Adayımız,
|
||||||
|
|
||||||
|
Bu teknik ödev, yazılım geliştirme becerilerinizi değerlendirmek için hazırlanmıştır. Amacımız, **mevcut kötü yazılmış bir randevu sistemini temiz kod prensiplerine uygun hale getirmek** ve geliştirme yaklaşımınızı görmek.
|
||||||
|
|
||||||
|
## 🎯 Göreviniz
|
||||||
|
Size verilen **Express.js + Vue.js + SQLite ile yazılmış kötü bir randevu sistemi** var. Bu projeyi **hatalarından arındırarak ve okunabilir hale getirerek** tekrar yazmalısınız.
|
||||||
|
|
||||||
|
## 📌 Mutlaka Yapılması Gerekenler (Zorunlu)
|
||||||
|
✅ **Kodun düzgün ve iyi çalışan bir hale getirilmesi gerekiyor.**
|
||||||
|
✅ **Veritabanı olarak SQLite kullanılmıştır. Bunu MongoDB ile değiştirebilirsiniz.**
|
||||||
|
✅ **Form doğrulaması (validation) zorunludur.**
|
||||||
|
✅ **Aynı tarih ve saate tekrar randevu alınmasını engellemelisiniz.**
|
||||||
|
✅ **Rezervasyon yapıldığında sayfa otomatik olarak güncellenmelidir.**
|
||||||
|
✅ **Backend ve frontend düzgün API entegrasyonu sağlamalıdır.**
|
||||||
|
✅ **Concurrency ve race condition problemleri göz önünde bulundurulmalı, önlemler alınmalıdır.**
|
||||||
|
✅ **Error handling düzgün yapılmalı, güvenlik açıkları minimize edilmelidir.**
|
||||||
|
✅ **Inline CSS kullanılmamalıdır, CSS dosyaları ayrılmalıdır.**
|
||||||
|
✅ **README dosyasında kurulum ve çalıştırma adımları eksiksiz açıklanmalıdır.**
|
||||||
|
|
||||||
|
## ⭐ Artı Değer Kazandıracaklar
|
||||||
|
🔹 **Nuxt.js ve MongoDB kullanmak.**
|
||||||
|
🔹 **Kod organizasyonunu geliştirirmek ve kod yapısını standart yaklaşımlara göre düzenlemek.**
|
||||||
|
🔹 **Ek çözümler geliştirmek. Örneğin, WebSockets (Socket.io) gibi gerçek zamanlı bir güncelleme yöntemi kullanabilirsiniz.**
|
||||||
|
🔹 **Tailwind CSS kullanarak UI tasarımını geliştirirmek.**
|
||||||
|
🔹 **Authentication ekleyerek kullanıcıların birbirinin randevularını görmesini engellerseniz değerlendirmede avantaj sağlar.**
|
||||||
|
🔹 **Projede yaşadığınız zorlukları, projede gördüğünüz problemlere dair çözümleri anlatan bir doküman bizimle paylaşmak.**
|
||||||
|
🔹 **Canlı bir demo (Vercel, Netlify, DigitalOcean vb.) oluşturursanız artı puan alırsınız.**
|
||||||
|
|
||||||
|
## 📦 Teslimat ve Süre
|
||||||
|
📅 **Teslim süresi:** 5 gün
|
||||||
|
📩 **Teslim şekli:**
|
||||||
|
- GitHub üzerinden paylaşabilirsiniz veya e-posta ile gönderebilirsiniz.
|
||||||
|
- README dosyanız mutlaka olmalıdır.
|
||||||
|
|
||||||
|
Başarılar! 🚀
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
.env.development
|
||||||
|
.env.test
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# Database
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Compiled binary addons
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.swn
|
||||||
|
.DS_Store
|
||||||
|
db.sqlite
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.21.2",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
const express = require('express');
|
||||||
|
const sqlite3 = require('sqlite3');
|
||||||
|
const cors = require('cors');
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
function getDb() {
|
||||||
|
return new sqlite3.Database('db.sqlite', (err) => {
|
||||||
|
if (err) console.log('db error:', err);
|
||||||
|
console.log('Connected to database!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = getDb();
|
||||||
|
db.run(
|
||||||
|
`CREATE TABLE IF NOT EXISTS appointments (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
date TEXT,
|
||||||
|
time TEXT,
|
||||||
|
name TEXT
|
||||||
|
)`,
|
||||||
|
(err) => {
|
||||||
|
console.log('Table creation error:', err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
app.post('/api/appointments', (req, res) => {
|
||||||
|
const d = req.body;
|
||||||
|
console.log('data:', d);
|
||||||
|
|
||||||
|
const db = getDb();
|
||||||
|
db.run(
|
||||||
|
'INSERT INTO appointments (date, time, name) VALUES (?, ?, ?)',
|
||||||
|
[d.date, d.time, d.name],
|
||||||
|
function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.log('error:', err);
|
||||||
|
res.status(500).json({ msg: 'error' });
|
||||||
|
} else {
|
||||||
|
db.all('SELECT * FROM appointments', [], (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
console.log('error:', err);
|
||||||
|
res.status(500).json({ msg: 'error' });
|
||||||
|
} else {
|
||||||
|
console.log('all data:', rows);
|
||||||
|
res.json({ msg: 'ok', data: rows });
|
||||||
|
}
|
||||||
|
db.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/slots', (req, res) => {
|
||||||
|
const db = getDb();
|
||||||
|
|
||||||
|
db.all('SELECT * FROM appointments', [], (err, rows) => {
|
||||||
|
let temp = [];
|
||||||
|
for (let i = 28; i <= 29; i++) {
|
||||||
|
for (let h = 13; h <= 17; h++) {
|
||||||
|
for (let m = 0; m < 60; m += 30) {
|
||||||
|
let d = `2024-02-${i}`;
|
||||||
|
let t = `${h}:${m === 0 ? '00' : m}`;
|
||||||
|
temp.push({ date: d, time: t });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
for (let h = 13; h <= 17; h++) {
|
||||||
|
for (let m = 0; m < 60; m += 30) {
|
||||||
|
let d = `2024-03-0${i}`;
|
||||||
|
let t = `${h}:${m === 0 ? '00' : m}`;
|
||||||
|
temp.push({ date: d, time: t });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let available = temp.filter((slot) => {
|
||||||
|
return !rows.some((r) => r.date === slot.date && r.time === slot.time);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('available slots:', available);
|
||||||
|
res.json(available);
|
||||||
|
db.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/appointments', (req, res) => {
|
||||||
|
const db = getDb();
|
||||||
|
db.all('SELECT * FROM appointments', [], (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
console.log('error:', err);
|
||||||
|
res.status(500).json({ msg: 'error' });
|
||||||
|
} else {
|
||||||
|
console.log('all appointments:', rows);
|
||||||
|
res.json(rows);
|
||||||
|
}
|
||||||
|
db.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000, () => console.log('Server running at http://localhost:3000'));
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
.vscode
|
|
@ -0,0 +1,29 @@
|
||||||
|
# frontend
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"vue": "^3.5.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"vite": "^6.1.0",
|
||||||
|
"vite-plugin-vue-devtools": "^7.7.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
<template>
|
||||||
|
<div style="padding: 20px; font-family: Arial; background-color: #f0f0f0; min-height: 100vh;">
|
||||||
|
<!-- Bad practice: Inline styles everywhere -->
|
||||||
|
<h1 style="color: blue; text-align: center; margin-bottom: 30px;">Randevu Sistemi</h1>
|
||||||
|
|
||||||
|
<!-- Bad practice: No form validation -->
|
||||||
|
<div
|
||||||
|
style="max-width: 600px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1);">
|
||||||
|
<input v-model="n" style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ddd;"
|
||||||
|
placeholder="Adınız">
|
||||||
|
|
||||||
|
<!-- Bad practice: Poor variable names -->
|
||||||
|
<select v-model="d" style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ddd;">
|
||||||
|
<option value="">Tarih Seçin</option>
|
||||||
|
<option v-for="s in slots" :key="s.date + s.time" :value="s.date">
|
||||||
|
{{ s.date }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select v-model="t" style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ddd;">
|
||||||
|
<option value="">Saat Seçin</option>
|
||||||
|
<option v-for="s in filteredTimes" :key="s.time" :value="s.time">
|
||||||
|
{{ s.time }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Bad practice: No loading states or error handling -->
|
||||||
|
<button @click="save"
|
||||||
|
style="width: 100%; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||||
|
Kaydet
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bad practice: No pagination or filtering -->
|
||||||
|
<div
|
||||||
|
style="max-width: 600px; margin: 20px auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1);">
|
||||||
|
<h2 style="margin-bottom: 20px;">Mevcut Randevular</h2>
|
||||||
|
<div v-for="a in appointments" :key="a.id" style="padding: 10px; border-bottom: 1px solid #ddd;">
|
||||||
|
{{ a.name }} - {{ a.date }} {{ a.time }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
n: '',
|
||||||
|
d: '',
|
||||||
|
t: '',
|
||||||
|
slots: [],
|
||||||
|
appointments: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredTimes() {
|
||||||
|
if (!this.d) return []
|
||||||
|
const temp = this.slots.filter(s => s.date === this.d)
|
||||||
|
console.log('filtered times:', temp)
|
||||||
|
return temp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchData()
|
||||||
|
this.fetchSlots()
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
console.log('polling...')
|
||||||
|
this.fetchData()
|
||||||
|
}, 5000)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const res = await fetch('http://localhost:3000/api/appointments')
|
||||||
|
const data = await res.json()
|
||||||
|
console.log('appointments:', data)
|
||||||
|
this.appointments = data
|
||||||
|
},
|
||||||
|
async fetchSlots() {
|
||||||
|
const res = await fetch('http://localhost:3000/api/slots')
|
||||||
|
const data = await res.json()
|
||||||
|
console.log('slots:', data)
|
||||||
|
this.slots = data
|
||||||
|
},
|
||||||
|
async save() {
|
||||||
|
const res = await fetch('http://localhost:3000/api/appointments', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.n,
|
||||||
|
date: this.d,
|
||||||
|
time: this.t
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
console.log('save response:', data)
|
||||||
|
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { createApp } from 'vue';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
console.log('Starting app...');
|
||||||
|
createApp(App).mount('#app');
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
Loading…
Reference in New Issue