Initial commit
This commit is contained in:
15
Tasker.Ui/.eslintrc.cjs
Normal file
15
Tasker.Ui/.eslintrc.cjs
Normal file
@@ -0,0 +1,15 @@
|
||||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
'extends': [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/eslint-config-typescript',
|
||||
'@vue/eslint-config-prettier/skip-formatting'
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest'
|
||||
}
|
||||
}
|
||||
30
Tasker.Ui/.gitignore
vendored
Normal file
30
Tasker.Ui/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# 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
|
||||
8
Tasker.Ui/.prettierrc.json
Normal file
8
Tasker.Ui/.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
7
Tasker.Ui/.vscode/extensions.json
vendored
Normal file
7
Tasker.Ui/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
45
Tasker.Ui/README.md
Normal file
45
Tasker.Ui/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Tasker.Ui
|
||||
|
||||
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).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||
|
||||
```sh
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
||||
1
Tasker.Ui/env.d.ts
vendored
Normal file
1
Tasker.Ui/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
16
Tasker.Ui/index.html
Normal file
16
Tasker.Ui/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Welcome to Vuetify 3</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
6804
Tasker.Ui/package-lock.json
generated
Normal file
6804
Tasker.Ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
Tasker.Ui/package.json
Normal file
50
Tasker.Ui/package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "tasker-ui",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "vitest",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"axios": "^1.6.7",
|
||||
"pinia": "^2.1.7",
|
||||
"uuidv7": "^1.0.1",
|
||||
"vue": "^3.4.29",
|
||||
"vue-router": "^4.3.3",
|
||||
"vuetify": "^3.5.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.8.0",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^20.14.5",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"npm-run-all2": "^6.2.0",
|
||||
"postcss": "^8.4.41",
|
||||
"prettier": "^3.2.5",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"typescript": "~5.4.0",
|
||||
"vite": "^5.3.1",
|
||||
"vite-plugin-vue-devtools": "^7.3.1",
|
||||
"vitest": "^1.6.0",
|
||||
"vue-tsc": "^2.0.21"
|
||||
}
|
||||
}
|
||||
6
Tasker.Ui/postcss.config.js
Normal file
6
Tasker.Ui/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
Tasker.Ui/public/favicon.ico
Normal file
BIN
Tasker.Ui/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
100
Tasker.Ui/src/App.vue
Normal file
100
Tasker.Ui/src/App.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-layout>
|
||||
|
||||
<v-system-bar app
|
||||
color="secondary">
|
||||
</v-system-bar>
|
||||
|
||||
<v-navigation-drawer
|
||||
app
|
||||
v-model="drawer"
|
||||
:rail="rail"
|
||||
permanent
|
||||
@click="rail = !rail"
|
||||
>
|
||||
<v-list-item
|
||||
prepend-avatar="https://randomuser.me/api/portraits/men/85.jpg"
|
||||
title="John Leider"
|
||||
>
|
||||
|
||||
</v-list-item>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list density="compact" nav>
|
||||
<v-list-item prepend-icon="mdi-home-city"
|
||||
title="Inbox"
|
||||
value="home"
|
||||
></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-account"
|
||||
title="Today"
|
||||
value="account"
|
||||
></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-account-group-outline"
|
||||
title="Upcoming"
|
||||
value="users"
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-app-bar app
|
||||
color="primary"
|
||||
prominent>
|
||||
|
||||
<v-app-bar-nav-icon variant="text"
|
||||
@click.stop="drawer = !drawer">
|
||||
</v-app-bar-nav-icon>
|
||||
|
||||
<v-toolbar-title>
|
||||
My files
|
||||
</v-toolbar-title>
|
||||
|
||||
</v-app-bar>
|
||||
|
||||
<v-main>
|
||||
<v-container fluid>
|
||||
<router-view></router-view>
|
||||
</v-container>
|
||||
</v-main>
|
||||
|
||||
<v-footer app class="flex bg-amber font-mono justify-center">
|
||||
©2024 Tasker
|
||||
</v-footer>
|
||||
|
||||
</v-layout>
|
||||
|
||||
</v-app>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const drawer = ref(true)
|
||||
const rail = ref(false)
|
||||
const group = ref(null)
|
||||
const items = ref([
|
||||
{
|
||||
title: 'Foo',
|
||||
value: 'foo'
|
||||
},
|
||||
{
|
||||
title: 'Bar',
|
||||
value: 'bar'
|
||||
},
|
||||
{
|
||||
title: 'Fizz',
|
||||
value: 'fizz'
|
||||
},
|
||||
{
|
||||
title: 'Buzz',
|
||||
value: 'buzz'
|
||||
}
|
||||
])
|
||||
|
||||
watch(group, () => {
|
||||
drawer.value = false
|
||||
})
|
||||
|
||||
</script>
|
||||
120
Tasker.Ui/src/api/taskApi.ts
Normal file
120
Tasker.Ui/src/api/taskApi.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
name: string; // [MaxLength(255)]
|
||||
description?: string | null; // [MaxLength(2048)]
|
||||
dueDate?: string | null;
|
||||
}
|
||||
|
||||
export interface TaskApiClient {
|
||||
getTasks(): Promise<Task[]>;
|
||||
|
||||
addTask(task: Task): Promise<Task>;
|
||||
}
|
||||
|
||||
export class TaskApiClientImpl implements TaskApiClient {
|
||||
private readonly baseUrl: string
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
this.baseUrl = baseUrl
|
||||
}
|
||||
|
||||
async getTasks(): Promise<Task[]> {
|
||||
const response = await fetch(`${this.baseUrl}/tasks`)
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch tasks')
|
||||
}
|
||||
const data = await response.json()
|
||||
return data as Task[]
|
||||
}
|
||||
|
||||
async addTask(task: Task): Promise<Task> {
|
||||
const response = await fetch(`${this.baseUrl}/tasks`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(task)
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to add task')
|
||||
}
|
||||
const data = await response.json()
|
||||
return data as Task
|
||||
}
|
||||
|
||||
async completeTask(id: string): Promise<Task> {
|
||||
const response = await fetch(`${this.baseUrl}/tasks/${id}/complete`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
'CompleteOn': new Date().toISOString()
|
||||
})
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to add task')
|
||||
}
|
||||
const data = await response.json()
|
||||
return data as Task
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function useTasks() {
|
||||
const tasks = ref<Task[]>([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const taskApiClient = new TaskApiClientImpl('https://localhost:7055')
|
||||
|
||||
const fetchTasks = async () => {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
tasks.value = await taskApiClient.getTasks()
|
||||
} catch (err) {
|
||||
error.value = (err as Error).message
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const addTask = async (task: Task) => {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const newTask = await taskApiClient.addTask(task)
|
||||
tasks.value.push(newTask)
|
||||
} catch (err) {
|
||||
error.value = (err as Error).message
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const completeTask = async (id: string) => {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
await taskApiClient.completeTask(id)
|
||||
} catch (err) {
|
||||
error.value = (err as Error).message
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchTasks)
|
||||
|
||||
return {
|
||||
tasks,
|
||||
isLoading,
|
||||
error,
|
||||
fetchTasks,
|
||||
addTask,
|
||||
completeTask
|
||||
}
|
||||
}
|
||||
86
Tasker.Ui/src/assets/base.css
Normal file
86
Tasker.Ui/src/assets/base.css
Normal file
@@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
1
Tasker.Ui/src/assets/logo.svg
Normal file
1
Tasker.Ui/src/assets/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||
|
After Width: | Height: | Size: 276 B |
35
Tasker.Ui/src/assets/main.css
Normal file
35
Tasker.Ui/src/assets/main.css
Normal file
@@ -0,0 +1,35 @@
|
||||
/*@import './base.css';*/
|
||||
|
||||
/*#app {*/
|
||||
/* max-width: 1280px;*/
|
||||
/* margin: 0 auto;*/
|
||||
/* padding: 2rem;*/
|
||||
/* font-weight: normal;*/
|
||||
/*}*/
|
||||
|
||||
/*a,*/
|
||||
/*.green {*/
|
||||
/* text-decoration: none;*/
|
||||
/* color: hsla(160, 100%, 37%, 1);*/
|
||||
/* transition: 0.4s;*/
|
||||
/* padding: 3px;*/
|
||||
/*}*/
|
||||
|
||||
/*@media (hover: hover) {*/
|
||||
/* a:hover {*/
|
||||
/* background-color: hsla(160, 100%, 37%, 0.2);*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
/*@media (min-width: 1024px) {*/
|
||||
/* body {*/
|
||||
/* display: flex;*/
|
||||
/* place-items: center;*/
|
||||
/* }*/
|
||||
|
||||
/* #app {*/
|
||||
/* display: grid;*/
|
||||
/* grid-template-columns: 1fr 1fr;*/
|
||||
/* padding: 0 2rem;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
72
Tasker.Ui/src/components/TaskInput.vue
Normal file
72
Tasker.Ui/src/components/TaskInput.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { type Task, useTasks } from '@/api/taskApi'
|
||||
import { uuidv7 } from 'uuidv7'
|
||||
|
||||
const name = ref('')
|
||||
const description = ref(null)
|
||||
const dueDate = ref(null)
|
||||
|
||||
const { addTask } = useTasks()
|
||||
|
||||
const doAddTask = () => {
|
||||
const newTask: Task = {
|
||||
id: uuidv7(),
|
||||
name: name.value,
|
||||
description: description.value,
|
||||
dueDate: dueDate.value
|
||||
}
|
||||
addTask(newTask)
|
||||
}
|
||||
|
||||
const cancelAddTask = () => {
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<v-text-field label="Task name"
|
||||
v-model="name"
|
||||
variant="underlined">
|
||||
</v-text-field>
|
||||
|
||||
<v-text-field label="Description"
|
||||
v-model="description"
|
||||
variant="underlined">
|
||||
</v-text-field>
|
||||
|
||||
<div class="flex flex-row space-x-2">
|
||||
|
||||
<span>due-date</span>
|
||||
|
||||
<span>priority</span>
|
||||
<span>tags</span>
|
||||
<span>reminders</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class=" bg-teal-400 p-2 flex flex-row space-x-2 justify-end">
|
||||
|
||||
<span class="justify-start">projects</span>
|
||||
|
||||
<div class="bg-red space-x-2 justify-end">
|
||||
<v-btn @click="cancelAddTask()"
|
||||
variant="outlined">
|
||||
Cancel
|
||||
</v-btn>
|
||||
|
||||
<v-btn @click="doAddTask()"
|
||||
color="primary"
|
||||
variant="flat">
|
||||
Add Task
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
44
Tasker.Ui/src/components/TaskList.vue
Normal file
44
Tasker.Ui/src/components/TaskList.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { useTasks } from '@/api/taskApi'
|
||||
|
||||
const { completeTask, tasks, isLoading, error } = useTasks()
|
||||
|
||||
function itemCompleted(id: string) {
|
||||
console.log(`completing task: ${id}`)
|
||||
completeTask(id)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list v-if="!isLoading && !error">
|
||||
<v-list-item v-for="task in tasks" :key="task.id">
|
||||
<div class="flex p-1 bg-amber">
|
||||
|
||||
<div>
|
||||
<v-checkbox-btn @change="itemCompleted(task.id)"
|
||||
></v-checkbox-btn>
|
||||
</div>
|
||||
|
||||
<div class="flex-column">
|
||||
<div class="">
|
||||
<div class="font-sans">{{ task.name }}</div>
|
||||
<div class="font-serif font-normal">Description {{ task.description }}</div>
|
||||
</div>
|
||||
|
||||
<div>{{ task.dueDate }}</div>
|
||||
<!-- <div>{{ task.project.Name }}</div>-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-alert v-else-if="error" type="error">
|
||||
{{ error }}
|
||||
</v-alert>
|
||||
<v-progress-circular v-else indeterminate></v-progress-circular>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
4
Tasker.Ui/src/index.css
Normal file
4
Tasker.Ui/src/index.css
Normal file
@@ -0,0 +1,4 @@
|
||||
/* ./src/index.css */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
18
Tasker.Ui/src/main.ts
Normal file
18
Tasker.Ui/src/main.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import vuetify from './vuetify'
|
||||
import 'vuetify/styles' // Ensure this line is present
|
||||
import '@mdi/font/css/materialdesignicons.css' // Import the Material Design Icons CSS
|
||||
import './assets/main.css'
|
||||
import './index.css'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(vuetify)
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
23
Tasker.Ui/src/router/index.ts
Normal file
23
Tasker.Ui/src/router/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/AboutView.vue')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
||||
12
Tasker.Ui/src/stores/counter.ts
Normal file
12
Tasker.Ui/src/stores/counter.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {ref, computed} from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
||||
16
Tasker.Ui/src/views/AboutView.vue
Normal file
16
Tasker.Ui/src/views/AboutView.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
<p class="bg-red-400">Let's try!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
14
Tasker.Ui/src/views/HomeView.vue
Normal file
14
Tasker.Ui/src/views/HomeView.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import {useCounterStore} from "@/stores/counter";
|
||||
import TaskInput from "@/components/TaskInput.vue";
|
||||
import TaskList from '@/components/TaskList.vue'
|
||||
|
||||
const counterStore = useCounterStore()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<task-input></task-input>
|
||||
<task-list></task-list>
|
||||
|
||||
</template>
|
||||
20
Tasker.Ui/src/vuetify.ts
Normal file
20
Tasker.Ui/src/vuetify.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'vuetify/styles'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import { aliases, mdi } from 'vuetify/iconsets/mdi'
|
||||
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
directives,
|
||||
icons: {
|
||||
defaultSet: 'mdi',
|
||||
aliases,
|
||||
sets: {
|
||||
mdi,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default vuetify
|
||||
12
Tasker.Ui/tailwind.config.js
Normal file
12
Tasker.Ui/tailwind.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
'./public/**/*.html',
|
||||
'./src/**/*.{js,jsx,ts,tsx,vue}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
14
Tasker.Ui/tsconfig.app.json
Normal file
14
Tasker.Ui/tsconfig.app.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Tasker.Ui/tsconfig.json
Normal file
14
Tasker.Ui/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.vitest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
Tasker.Ui/tsconfig.node.json
Normal file
19
Tasker.Ui/tsconfig.node.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
11
Tasker.Ui/tsconfig.vitest.json
Normal file
11
Tasker.Ui/tsconfig.vitest.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"exclude": [],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
||||
|
||||
"lib": [],
|
||||
"types": ["node", "jsdom"]
|
||||
}
|
||||
}
|
||||
18
Tasker.Ui/vite.config.ts
Normal file
18
Tasker.Ui/vite.config.ts
Normal file
@@ -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://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
})
|
||||
14
Tasker.Ui/vitest.config.ts
Normal file
14
Tasker.Ui/vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
||||
import viteConfig from './vite.config'
|
||||
|
||||
export default mergeConfig(
|
||||
viteConfig,
|
||||
defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||
root: fileURLToPath(new URL('./', import.meta.url))
|
||||
}
|
||||
})
|
||||
)
|
||||
5
Tasker/.gitignore
vendored
Normal file
5
Tasker/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
13
Tasker/.idea/.idea.Tasker/.idea/.gitignore
generated
vendored
Normal file
13
Tasker/.idea/.idea.Tasker/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/contentModel.xml
|
||||
/.idea.Tasker.iml
|
||||
/projectSettingsUpdater.xml
|
||||
/modules.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
12
Tasker/.idea/.idea.Tasker/.idea/dataSources.xml
generated
Normal file
12
Tasker/.idea/.idea.Tasker/.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="@localhost" uuid="3dea8d67-b2f5-4fc2-915f-7e72ddcf11e8">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://localhost:5432/</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
4
Tasker/.idea/.idea.Tasker/.idea/encodings.xml
generated
Normal file
4
Tasker/.idea/.idea.Tasker/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
Tasker/.idea/.idea.Tasker/.idea/indexLayout.xml
generated
Normal file
8
Tasker/.idea/.idea.Tasker/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
7
Tasker/.idea/.idea.Tasker/.idea/vcs.xml
generated
Normal file
7
Tasker/.idea/.idea.Tasker/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
10
Tasker/Tasker.Web/Data/ProjectingDbContext.cs
Normal file
10
Tasker/Tasker.Web/Data/ProjectingDbContext.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Tasker.Web.Data;
|
||||
|
||||
public class ProjectingDbContext(
|
||||
DbContextOptions options)
|
||||
: DbContext(options)
|
||||
{
|
||||
public DbSet<Task> Tasks { get; set; }
|
||||
}
|
||||
12
Tasker/Tasker.Web/Data/Task.cs
Normal file
12
Tasker/Tasker.Web/Data/Task.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tasker.Web.Data;
|
||||
|
||||
public class Task
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(255)] public required string Name { get; set; }
|
||||
[MaxLength(2048)] public string? Description { get; set; }
|
||||
public DateTimeOffset? DueDate { get; set; }
|
||||
public DateTimeOffset? CompletedOn { get; set; }
|
||||
}
|
||||
46
Tasker/Tasker.Web/Endpoints/AddTask.cs
Normal file
46
Tasker/Tasker.Web/Endpoints/AddTask.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Tasker.Web.Data;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Tasker.Web.Endpoints;
|
||||
|
||||
[PublicAPI]
|
||||
public record AddTaskRequest(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? Description,
|
||||
DateTimeOffset? DueDate);
|
||||
|
||||
[PublicAPI]
|
||||
public class AddTaskEndpoint(
|
||||
ProjectingDbContext dbContext)
|
||||
: Endpoint<AddTaskRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/tasks");
|
||||
Options(o => o.WithTags("Projecting"));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
AddTaskRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var task = await dbContext.Tasks.AddAsync(
|
||||
new Data.Task
|
||||
{
|
||||
Id = req.Id,
|
||||
Name = req.Name,
|
||||
Description = req.Description,
|
||||
DueDate = req.DueDate
|
||||
},
|
||||
cancellationToken: ct);
|
||||
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
await SendCreatedAtAsync<GetTaskEndpoint>(
|
||||
task.Entity.Id,
|
||||
task.Entity,
|
||||
cancellation: ct);
|
||||
}
|
||||
}
|
||||
47
Tasker/Tasker.Web/Endpoints/CompleteTask.cs
Normal file
47
Tasker/Tasker.Web/Endpoints/CompleteTask.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Tasker.Web.Data;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Tasker.Web.Endpoints;
|
||||
|
||||
[PublicAPI]
|
||||
public record CompleteTaskRequest(
|
||||
Guid Id,
|
||||
DateTimeOffset CompleteOn);
|
||||
|
||||
[PublicAPI]
|
||||
public class CompleteTaskEndpoint(
|
||||
ProjectingDbContext dbContext)
|
||||
: Endpoint<CompleteTaskRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/tasks/{Id}/complete");
|
||||
Options(o => o.WithTags("Projecting"));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CompleteTaskRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var task = await dbContext
|
||||
.Tasks
|
||||
.FindAsync(
|
||||
[req.Id],
|
||||
cancellationToken: ct);
|
||||
|
||||
if (task is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
task.CompletedOn = req.CompleteOn;
|
||||
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(
|
||||
task,
|
||||
cancellation: ct);
|
||||
}
|
||||
}
|
||||
32
Tasker/Tasker.Web/Endpoints/GetTask.cs
Normal file
32
Tasker/Tasker.Web/Endpoints/GetTask.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Tasker.Web.Data;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Tasker.Web.Endpoints;
|
||||
|
||||
[PublicAPI]
|
||||
public record GetTaskRequest(
|
||||
Guid Id);
|
||||
|
||||
[PublicAPI]
|
||||
public class GetTaskEndpoint(
|
||||
ProjectingDbContext dbContext)
|
||||
: Endpoint<GetTaskRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/tasks/{Id}");
|
||||
Options(o => o.WithTags("Projecting"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
GetTaskRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var task = await dbContext.FindAsync<GetTaskRequest>(
|
||||
req.Id);
|
||||
|
||||
await SendOkAsync(
|
||||
task,
|
||||
cancellation: ct);
|
||||
}
|
||||
}
|
||||
32
Tasker/Tasker.Web/Endpoints/GetTasks.cs
Normal file
32
Tasker/Tasker.Web/Endpoints/GetTasks.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tasker.Web.Data;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Tasker.Web.Endpoints;
|
||||
|
||||
[PublicAPI]
|
||||
public class GetTasksEndpoint(
|
||||
ProjectingDbContext dbContext)
|
||||
: EndpointWithoutRequest
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/tasks");
|
||||
Options(o => o.WithTags("Projecting"));
|
||||
AllowAnonymous();
|
||||
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tasks = await dbContext
|
||||
.Tasks
|
||||
.Where(t => t.CompletedOn == null)
|
||||
.ToListAsync(cancellationToken: ct);
|
||||
|
||||
await SendOkAsync(
|
||||
tasks,
|
||||
cancellation: ct);
|
||||
}
|
||||
}
|
||||
2
Tasker/Tasker.Web/GlobalUsings.cs
Normal file
2
Tasker/Tasker.Web/GlobalUsings.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
global using FastEndpoints;
|
||||
global using JetBrains.Annotations;
|
||||
53
Tasker/Tasker.Web/Migrations/20240811051653_Initial.Designer.cs
generated
Normal file
53
Tasker/Tasker.Web/Migrations/20240811051653_Initial.Designer.cs
generated
Normal file
@@ -0,0 +1,53 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Tasker.Web.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Tasker.Web.Migrations
|
||||
{
|
||||
[DbContext(typeof(ProjectingDbContext))]
|
||||
[Migration("20240811051653_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Tasker.Web.Data.Task", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<DateTimeOffset?>("DueDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Tasks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Tasker/Tasker.Web/Migrations/20240811051653_Initial.cs
Normal file
36
Tasker/Tasker.Web/Migrations/20240811051653_Initial.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Tasker.Web.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tasks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
DueDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tasks", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tasks");
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Tasker/Tasker.Web/Migrations/20240812185703_AddCompletedOn.Designer.cs
generated
Normal file
56
Tasker/Tasker.Web/Migrations/20240812185703_AddCompletedOn.Designer.cs
generated
Normal file
@@ -0,0 +1,56 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Tasker.Web.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Tasker.Web.Migrations
|
||||
{
|
||||
[DbContext(typeof(ProjectingDbContext))]
|
||||
[Migration("20240812185703_AddCompletedOn")]
|
||||
partial class AddCompletedOn
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Tasker.Web.Data.Task", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("CompletedOn")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<DateTimeOffset?>("DueDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Tasks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Tasker.Web.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCompletedOn : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "CompletedOn",
|
||||
table: "Tasks",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CompletedOn",
|
||||
table: "Tasks");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Tasker.Web.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Tasker.Web.Migrations
|
||||
{
|
||||
[DbContext(typeof(ProjectingDbContext))]
|
||||
partial class ProjectingDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Tasker.Web.Data.Task", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("CompletedOn")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<DateTimeOffset?>("DueDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Tasks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Tasker/Tasker.Web/Program.cs
Normal file
43
Tasker/Tasker.Web/Program.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tasker.Web.Data;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddFastEndpoints();
|
||||
builder.Services.AddDbContext<ProjectingDbContext>(o =>
|
||||
o.UseNpgsql(builder.Configuration.GetConnectionString("ProjectingDbContext")));
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowSpecificOrigin",
|
||||
policy =>
|
||||
{
|
||||
policy.WithOrigins("http://localhost:5173")
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseCors("AllowSpecificOrigin");
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
|
||||
// specifying the Swagger JSON endpoint
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
|
||||
c.RoutePrefix = string.Empty; // Set Swagger UI at app's root
|
||||
});
|
||||
}
|
||||
|
||||
app.UseFastEndpoints();
|
||||
|
||||
app.Run();
|
||||
25
Tasker/Tasker.Web/Properties/launchSettings.json
Normal file
25
Tasker/Tasker.Web/Properties/launchSettings.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"launchUrl": "weatherforecast",
|
||||
"applicationUrl": "http://localhost:5239",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"launchUrl": "weatherforecast",
|
||||
"applicationUrl": "https://localhost:7055;http://localhost:5239",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Tasker/Tasker.Web/Tasker.Web.csproj
Normal file
18
Tasker/Tasker.Web/Tasker.Web.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FastEndpoints" Version="5.28.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
Tasker/Tasker.Web/Tasker.Web.http
Normal file
6
Tasker/Tasker.Web/Tasker.Web.http
Normal file
@@ -0,0 +1,6 @@
|
||||
@Tasker.Web_HostAddress = http://localhost:5239
|
||||
|
||||
GET {{Tasker.Web_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
11
Tasker/Tasker.Web/appsettings.Development.json
Normal file
11
Tasker/Tasker.Web/appsettings.Development.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"ProjectingDbContext": "Server=127.0.0.1;Port=5432;Database=Tasker;User Id=sa;Password=P@ssword123!;"
|
||||
}
|
||||
}
|
||||
9
Tasker/Tasker.Web/appsettings.json
Normal file
9
Tasker/Tasker.Web/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
16
Tasker/Tasker.sln
Normal file
16
Tasker/Tasker.sln
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tasker.Web", "Tasker.Web\Tasker.Web.csproj", "{5718F5B9-8EF2-4FE6-ABA4-21BC699F4023}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5718F5B9-8EF2-4FE6-ABA4-21BC699F4023}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5718F5B9-8EF2-4FE6-ABA4-21BC699F4023}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5718F5B9-8EF2-4FE6-ABA4-21BC699F4023}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5718F5B9-8EF2-4FE6-ABA4-21BC699F4023}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
7
Tasker/Tasker.sln.DotSettings.user
Normal file
7
Tasker/Tasker.sln.DotSettings.user
Normal file
@@ -0,0 +1,7 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AActivatorUtilities_002Ecs_002Fl_003AC_0021_003FUsers_003Flowra_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb3bfd1dceaf91cf199c7d231f873541c39abbdffae998d84b37f373cf411_003FActivatorUtilities_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkServiceCollectionExtensions_002Ecs_002Fl_003AC_0021_003FUsers_003Flowra_003FAppData_003FLocal_003FSymbols_003Fsrc_003Fdotnet_003Fefcore_003F0d1256be4658567c8a24b4c027bdbb3dbd6de656_003Fsrc_003FEFCore_003FExtensions_003FEntityFrameworkServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIListSource_002Ecs_002Fl_003AC_0021_003FUsers_003Flowra_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F95d8801ed7894a44a3b73e8b963694f8bd910_003Fb0_003Ff108f363_003FIListSource_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOpenApiEndpointRouteBuilderExtensions_002Ecs_002Fl_003AC_0021_003FUsers_003Flowra_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F5afdf3ad3bf2cb0b47575971c4deac89885ddeb3b2444d56faa3cbd78cd_003FOpenApiEndpointRouteBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollection_002Ecs_002Fl_003AC_0021_003FUsers_003Flowra_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F595e4f2361fd31cb66c7e9303f46cf8f2fe77d96eea9d0e42c17cae783aa74_003FServiceCollection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003AC_0021_003FUsers_003Flowra_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F658ad1fa108317d78bcae889b6e4f9dd5327babb6582972f2dbeabf36dafa5_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
Reference in New Issue
Block a user