initial commit. Cloned timetracker repository

This commit is contained in:
Daniel Goc
2026-03-10 09:02:57 +01:00
commit f2952bcef0
189 changed files with 21334 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
export const useCookie = () => {
function getCookie(name: string): string | null {
const cookies = document.cookie ? document.cookie.split('; ') : []
for (const cookie of cookies) {
const [key, ...rest] = cookie.split('=')
if (key === name) {
return decodeURIComponent(rest.join('='))
}
}
return null
}
function setCookie(
name: string,
value: string,
options?: {
days?: number
path?: string
domain?: string
secure?: boolean
sameSite?: 'Lax' | 'Strict' | 'None'
},
) {
let cookie = `${name}=${encodeURIComponent(value)}`
if (options?.days) {
const date = new Date()
date.setTime(date.getTime() + options.days * 24 * 60 * 60 * 1000)
cookie += `; expires=${date.toUTCString()}`
}
cookie += `; path=${options?.path ?? '/'}`
if (options?.domain) {
cookie += `; domain=${options.domain}`
}
if (options?.secure) {
cookie += `; Secure`
}
if (options?.sameSite) {
cookie += `; SameSite=${options.sameSite}`
}
document.cookie = cookie
}
function deleteCookie(name: string, path: string = '/', domain?: string) {
let cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}`
if (domain) {
cookie += `; domain=${domain}`
}
document.cookie = cookie
}
return {
getCookie,
setCookie,
deleteCookie,
}
}

View File

@@ -0,0 +1,77 @@
import type { Resp } from '@/types'
export async function useFetchJson<T = unknown>(url: string, opt?: RequestInit): Promise<Resp<T>> {
const prefix = import.meta.env.VITE_API_URL ?? ''
const urlFull = join(prefix, url)
const headers = new Headers(opt?.headers)
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
const fetchOptions: RequestInit = {
...opt,
headers,
// Always include cookies so the backend can read the HTTPOnly access_token
credentials: 'same-origin',
}
try {
const res = await fetch(urlFull, fetchOptions)
const contentType = res.headers.get('content-type') ?? ''
if (!contentType.includes('application/json')) {
throw { message: 'this is not proper json format' } as Resp<any>
}
const data = await res.json()
// Handle 401 — access token expired; try to refresh via the HTTPOnly refresh_token cookie
if (res.status === 401) {
const { useAuthStore } = await import('@/stores/auth')
const authStore = useAuthStore()
const refreshed = await authStore.refreshAccessToken()
if (refreshed) {
// Retry the original request — cookies are updated by the refresh endpoint
const retryRes = await fetch(urlFull, fetchOptions)
const retryContentType = retryRes.headers.get('content-type') ?? ''
if (!retryContentType.includes('application/json')) {
throw { message: 'this is not proper json format' } as Resp<any>
}
const retryData = await retryRes.json()
if (!retryRes.ok) {
throw retryData as Resp<any>
}
return retryData as Resp<T>
}
// Refresh failed — logout and propagate the error
authStore.logout()
throw data as Resp<any>
}
if (!res.ok) {
throw data as Resp<any>
}
return data as Resp<T>
} catch (error) {
throw error as Resp<any>
}
}
export function join(...parts: string[]): string {
const path = parts
.filter(Boolean)
.join('/')
.replace(/\/{2,}/g, '/')
return path.startsWith('/') ? path : `/${path}`
}

View File

@@ -0,0 +1,87 @@
import { useFetchJson } from './useFetchJson'
import type { Resp } from '@/types/response'
const API_PREFIX = '/api/v1/repo'
export interface QuarterData {
quarter: string
time: number
}
export interface IssueTimeSummary {
IssueID: number
IssueName: string
UserId: number
Initials: string
CreatedDate: string
TotalHoursSpent: number
}
export interface IssueResponse {
items: IssueTimeSummary[]
count: number
}
export interface PagingParams {
page?: number
pageSize?: number
}
export async function getRepos(): Promise<any> {
const result = await useFetchJson<number[]>(`${API_PREFIX}/get-repos`)
return result
}
// export async function getYears(repoID: number): Promise<any> {
// return useFetchJson<number[]>(`${API_PREFIX}/get-years?repoID=${repoID}`)
// }
// console.log(getYears(), 'leraaaaaa')
export async function getYears(repoID: number): Promise<any> {
return useFetchJson<number[]>(`${API_PREFIX}/get-years?repoID=${repoID}`);
}
// Correct way to log the data
export async function getQuarters(repoID: number, year: number): Promise<any> {
return useFetchJson<QuarterData[]>(`${API_PREFIX}/get-quarters?repoID=${repoID}&year=${year}`)
}
// export async function getIssues(
// repoID: number,
// year: number,
// quarter: number,
// page: number = 1,
// pageSize: number = 10
// ): Promise<any> {
// // The get-issues endpoint uses GET with pagination in query params
// return useFetchJson<IssueResponse>(
// `${API_PREFIX}/get-issues?repoID=${repoID}&year=${year}&quarter=${quarter}&page_number=${page}&elements_per_page=${pageSize}`
// )
// }
// async function logYears() {
// const years = await getIssues(7); // pass a repoID
// console.log(years, 'leraaaaaa');
// }
export async function getIssues(
repoID: number,
year: number,
quarter: number,
page: number = 1,
pageSize: number = 10
): Promise<any> {
return useFetchJson<IssueResponse>(
`${API_PREFIX}/get-issues?repoID=${repoID}&year=${year}&quarter=${quarter}&page_number=${page}&elements_per_page=${pageSize}`
);
}
// Correct logging function
async function logIssues() {
const repoID = 7;
const year = 2026; // example year
const quarter = 1; // example quarter
const issues = await getIssues(repoID, year, quarter);
console.log(issues, 'leraaaaaa');
}
logIssues();

View File

@@ -0,0 +1,98 @@
import type { Ref } from 'vue'
import type { FormError } from '@nuxt/ui'
import { settings } from '@/router/settings'
import { i18n } from '@/plugins/i18n'
export const useValidation = () => {
const errors = [] as FormError[]
function reset() {
errors.length = 0
}
function validateFirstName(first_name_ref: Ref<string>, name: string, message: string) {
if (!first_name_ref.value || !/^[A-Za-z]{2,}$/.test(first_name_ref.value)) {
errors.push({ name: name, message: message })
}
}
function validateLastName(last_name_ref: Ref<string>, name: string, message: string) {
if (!last_name_ref.value || !/^[A-Za-z]{2,}$/.test(last_name_ref.value)) {
errors.push({ name: name, message: message })
}
}
function validateEmail(email_ref: Ref<string>, name: string, message: string) {
if (!email_ref.value || !/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(email_ref.value)) {
errors.push({ name: name, message: message })
}
}
// function validatePasswords(
// password_ref: Ref<string>,
// password_name: string,
// confirm_password_ref: Ref<string>,
// confirm_name: string,
// message_password: string,
// message_confirm_password: string,
// ) {
// const regex = new RegExp(settings.app?.password_regex ?? '^.{8,}$')
// if (!password_ref.value) {
// errors.push({ name: password_name, message: message_password })
// } else if (!regex.test(password_ref.value)) {
// errors.push({
// name: password_name,
// message: 'general.registration_validation_password_requirements'
// })
// }
// if (!confirm_password_ref.value) {
// errors.push({ name: confirm_name, message: message_confirm_password })
// } else if (password_ref.value !== confirm_password_ref.value) {
// errors.push({
// name: confirm_name,
// message: 'registration_validation_password_not_same'
// })
// }
// }
function validatePasswords(
password_ref: Ref<string>,
password_name: string,
confirm_password_ref: Ref<string>,
confirm_name: string,
message_confirm_password: string,
) {
const regexPass = new RegExp(
'^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-=[\\]{};:\'",.<>/?]).{8,}$'
)
if (!password_ref.value) {
errors.push({ name: password_name, message: i18n.t('validate_error.password_required') })
} else if (!regexPass.test(password_ref.value)) {
errors.push({
name: password_name,
message: i18n.t('validate_error.registration_validation_password_requirements')
})
}
if (!confirm_password_ref.value) {
errors.push({ name: confirm_name, message: message_confirm_password })
} else if (password_ref.value !== confirm_password_ref.value) {
errors.push({
name: confirm_name,
message: i18n.t('validate_error.registration_validation_password_not_same')
})
}
}
return {
errors,
reset,
validateFirstName,
validateLastName,
validateEmail,
validatePasswords,
}
}