@strav/spring 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/package.json +31 -0
- package/src/index.ts +176 -0
- package/src/prompts.ts +135 -0
- package/src/scaffold.ts +54 -0
- package/src/templates/api/app/controllers/user_controller.ts +69 -0
- package/src/templates/api/config/http.ts +10 -0
- package/src/templates/api/index.ts +33 -0
- package/src/templates/api/routes/routes.ts +24 -0
- package/src/templates/shared/.env +14 -0
- package/src/templates/shared/app/controllers/controller.ts +15 -0
- package/src/templates/shared/app/models/user.ts +30 -0
- package/src/templates/shared/config/app.ts +10 -0
- package/src/templates/shared/config/database.ts +9 -0
- package/src/templates/shared/config/encryption.ts +5 -0
- package/src/templates/shared/database/factories/user_factory.ts +11 -0
- package/src/templates/shared/database/schemas/public/user.ts +13 -0
- package/src/templates/shared/database/seeders/database_seeder.ts +8 -0
- package/src/templates/shared/database/seeders/user_seeder.ts +15 -0
- package/src/templates/shared/package.json +24 -0
- package/src/templates/shared/routes/routes.ts +13 -0
- package/src/templates/shared/storage/cache/.gitkeep +1 -0
- package/src/templates/shared/storage/logs/.gitkeep +1 -0
- package/src/templates/shared/storage/uploads/.gitkeep +1 -0
- package/src/templates/shared/strav.ts +20 -0
- package/src/templates/shared/tests/example.test.ts +11 -0
- package/src/templates/shared/tsconfig.json +20 -0
- package/src/templates/web/app/controllers/home_controller.ts +24 -0
- package/src/templates/web/config/session.ts +10 -0
- package/src/templates/web/config/view.ts +7 -0
- package/src/templates/web/index.ts +48 -0
- package/src/templates/web/package.json +26 -0
- package/src/templates/web/resources/css/app.css +176 -0
- package/src/templates/web/resources/ts/islands/counter.vue +42 -0
- package/src/templates/web/resources/ts/islands/user_manager.vue +127 -0
- package/src/templates/web/resources/ts/islands/user_search.vue +71 -0
- package/src/templates/web/resources/views/layouts/app.strav +32 -0
- package/src/templates/web/resources/views/pages/home.strav +52 -0
- package/src/templates/web/resources/views/pages/users.strav +63 -0
- package/src/templates/web/routes/routes.ts +22 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="space-y-4">
|
|
3
|
+
<div class="relative">
|
|
4
|
+
<input
|
|
5
|
+
v-model="searchTerm"
|
|
6
|
+
type="text"
|
|
7
|
+
:placeholder="placeholder"
|
|
8
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
9
|
+
/>
|
|
10
|
+
<div class="absolute inset-y-0 right-0 pr-3 flex items-center">
|
|
11
|
+
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
13
|
+
</svg>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="text-sm text-gray-600">
|
|
18
|
+
<span v-if="searchTerm">
|
|
19
|
+
Searching for "{{ searchTerm }}"...
|
|
20
|
+
</span>
|
|
21
|
+
<span v-else>
|
|
22
|
+
Search through {{ userCount }} users
|
|
23
|
+
</span>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div v-if="searchTerm && searchResults.length > 0" class="border rounded-lg p-4 bg-gray-50">
|
|
27
|
+
<p class="text-sm font-medium text-gray-900 mb-2">Search Results:</p>
|
|
28
|
+
<ul class="space-y-1">
|
|
29
|
+
<li v-for="result in searchResults" :key="result" class="text-sm text-gray-700">
|
|
30
|
+
• {{ result }}
|
|
31
|
+
</li>
|
|
32
|
+
</ul>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div v-else-if="searchTerm" class="border rounded-lg p-4 bg-gray-50 text-center text-gray-500">
|
|
36
|
+
No results found for "{{ searchTerm }}"
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup>
|
|
42
|
+
import { ref, computed, watch } from 'vue'
|
|
43
|
+
|
|
44
|
+
const props = defineProps({
|
|
45
|
+
placeholder: { type: String, default: 'Search...' },
|
|
46
|
+
userCount: { type: Number, default: 0 }
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const searchTerm = ref('')
|
|
50
|
+
const searchResults = computed(() => {
|
|
51
|
+
if (!searchTerm.value) return []
|
|
52
|
+
|
|
53
|
+
// Mock search results - in a real app, this would call an API
|
|
54
|
+
const mockResults = [
|
|
55
|
+
'John Doe (john@example.com)',
|
|
56
|
+
'Jane Smith (jane@example.com)',
|
|
57
|
+
'Bob Johnson (bob@example.com)'
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
return mockResults.filter(result =>
|
|
61
|
+
result.toLowerCase().includes(searchTerm.value.toLowerCase())
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Demo of reactive watchers
|
|
66
|
+
watch(searchTerm, (newTerm) => {
|
|
67
|
+
if (newTerm) {
|
|
68
|
+
console.log(`Searching for: ${newTerm}`)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ title }} - __PROJECT_NAME__</title>
|
|
7
|
+
<link rel="stylesheet" href="{{ asset('/css/app.css') }}">
|
|
8
|
+
@stack('styles')
|
|
9
|
+
</head>
|
|
10
|
+
<body class="bg-gray-50">
|
|
11
|
+
<nav class="bg-white shadow">
|
|
12
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
13
|
+
<div class="flex justify-between h-16">
|
|
14
|
+
<div class="flex items-center">
|
|
15
|
+
<a href="/" class="text-xl font-bold text-gray-900">__PROJECT_NAME__</a>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="flex items-center space-x-4">
|
|
18
|
+
<a href="/" class="text-gray-600 hover:text-gray-900">Home</a>
|
|
19
|
+
<a href="/users" class="text-gray-600 hover:text-gray-900">Users</a>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</nav>
|
|
24
|
+
|
|
25
|
+
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
|
26
|
+
@show('content')
|
|
27
|
+
</main>
|
|
28
|
+
|
|
29
|
+
@islands
|
|
30
|
+
@stack('scripts')
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
@layout('layouts/app')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<div class="px-4 py-6 sm:px-0">
|
|
5
|
+
<div class="border-4 border-dashed border-gray-200 rounded-lg">
|
|
6
|
+
<div class="text-center py-12">
|
|
7
|
+
<h1 class="text-4xl font-bold text-gray-900 mb-4">{{ message }}</h1>
|
|
8
|
+
<p class="text-lg text-gray-600 mb-8">
|
|
9
|
+
You have successfully created a new Strav application. Start building something amazing!
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto mb-8">
|
|
13
|
+
<div class="bg-white p-6 rounded-lg shadow">
|
|
14
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">Database Ready</h3>
|
|
15
|
+
<p class="text-gray-600">{{ userCount }} users in the database</p>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="bg-white p-6 rounded-lg shadow">
|
|
19
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">Vue Islands</h3>
|
|
20
|
+
<p class="text-gray-600">Interactive components below</p>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="bg-white p-6 rounded-lg shadow">
|
|
24
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">TypeScript</h3>
|
|
25
|
+
<p class="text-gray-600">Full type safety everywhere</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
{{-- Demo Vue Islands --}}
|
|
30
|
+
<div class="space-y-6">
|
|
31
|
+
<h2 class="text-2xl font-bold text-gray-900">Vue Islands Demo</h2>
|
|
32
|
+
|
|
33
|
+
<div class="bg-white p-6 rounded-lg shadow max-w-md mx-auto">
|
|
34
|
+
<h3 class="text-lg font-semibold mb-4">Counter Component</h3>
|
|
35
|
+
<vue:counter :initial="5" label="Click me!" />
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="bg-white p-6 rounded-lg shadow max-w-md mx-auto">
|
|
39
|
+
<h3 class="text-lg font-semibold mb-4">User Search</h3>
|
|
40
|
+
<vue:user-search placeholder="Search users..." :userCount="{{ userCount }}" />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="mt-8">
|
|
45
|
+
<a href="/users" class="inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700">
|
|
46
|
+
View Users
|
|
47
|
+
</a>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
@end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@layout('layouts/app')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<div class="px-4 py-6 sm:px-0">
|
|
5
|
+
<div class="sm:flex sm:items-center">
|
|
6
|
+
<div class="sm:flex-auto">
|
|
7
|
+
<h1 class="text-2xl font-semibold text-gray-900">Users</h1>
|
|
8
|
+
<p class="mt-2 text-sm text-gray-700">
|
|
9
|
+
A list of all users in your application.
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="mt-8 flow-root">
|
|
15
|
+
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
|
16
|
+
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
|
17
|
+
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
|
|
18
|
+
<table class="min-w-full divide-y divide-gray-300">
|
|
19
|
+
<thead class="bg-gray-50">
|
|
20
|
+
<tr>
|
|
21
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Name</th>
|
|
22
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Email</th>
|
|
23
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Created</th>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
27
|
+
@each(user in users)
|
|
28
|
+
<tr @class([
|
|
29
|
+
'hover:bg-gray-50',
|
|
30
|
+
'bg-yellow-50' => $first,
|
|
31
|
+
])>
|
|
32
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
33
|
+
{{ user.name }}
|
|
34
|
+
</td>
|
|
35
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
36
|
+
{{ user.email }}
|
|
37
|
+
</td>
|
|
38
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
39
|
+
{{ user.created_at?.toLocaleDateString() }}
|
|
40
|
+
</td>
|
|
41
|
+
</tr>
|
|
42
|
+
@end
|
|
43
|
+
|
|
44
|
+
@if(users.length === 0)
|
|
45
|
+
<tr>
|
|
46
|
+
<td colspan="3" class="px-6 py-12 text-center text-sm text-gray-500">
|
|
47
|
+
No users found. Run <code class="bg-gray-100 px-2 py-1 rounded">bun strav seed</code> to add sample data.
|
|
48
|
+
</td>
|
|
49
|
+
</tr>
|
|
50
|
+
@end
|
|
51
|
+
</tbody>
|
|
52
|
+
</table>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{{-- Interactive user management with Vue island --}}
|
|
59
|
+
<div class="mt-8">
|
|
60
|
+
<vue:user-manager :initialUsers="{{ JSON.stringify(users) }}" />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
@end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Router } from '@strav/http'
|
|
2
|
+
import { staticFiles } from '@strav/http'
|
|
3
|
+
import HomeController from '../app/controllers/home_controller.ts'
|
|
4
|
+
|
|
5
|
+
export default function (router: Router) {
|
|
6
|
+
// Serve static files from public directory
|
|
7
|
+
router.use(staticFiles('public'))
|
|
8
|
+
|
|
9
|
+
// Web routes
|
|
10
|
+
router.get('/', [HomeController, 'index'])
|
|
11
|
+
router.get('/users', [HomeController, 'users'])
|
|
12
|
+
|
|
13
|
+
// Health check endpoint
|
|
14
|
+
router.get('/health', async (ctx) => {
|
|
15
|
+
return ctx.json({
|
|
16
|
+
status: 'ok',
|
|
17
|
+
timestamp: new Date().toISOString(),
|
|
18
|
+
app: '__PROJECT_NAME__',
|
|
19
|
+
version: '0.1.0'
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
}
|