Initial commit of testapi

This commit is contained in:
2025-06-20 13:33:07 +02:00
commit a455aba1d5
377 changed files with 30566 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\ApiAdmins;
class MkApiAdmin extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mkapiadmin';
/**
* The console command description.
*
* @var string
*/
protected $description = 'List users without an ApiAdmins record and assign one to a selected user';
public function handle() {
$usersWithoutAdmin = User::doesntHave('apiAdmin')->get(['id', 'name', 'email']);
if ($usersWithoutAdmin->isEmpty()) {
$this->info("All users already have an ApiAdmins entry.");
return 0;
}
$options = $usersWithoutAdmin->map(function ($user) {
return "{$user->id}: {$user->name} ({$user->email})";
})->toArray();
$chosenString = $this->choice(
'Select a user to make an API Admin',
$options,
0, // default to the first index
null,
false
);
[$chosenId] = explode(':', $chosenString, 2);
$chosenId = (int) trim($chosenId);
ApiAdmins::create([
'user_id' => $chosenId,
]);
$this->info("User [ID: {$chosenId}] has been assigned as an API Admin.");
return 0;
}
}
+454
View File
@@ -0,0 +1,454 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Carbon\Carbon;
use App\Models\SmartdokProfile;
use App\Models\Department;
use App\Models\Project;
use App\Models\WorkHour;
class MkDemoData extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mkdemodata
{--profiles : Generate SmartdokProfile demo data}
{--departments : Generate Department demo data}
{--projects : Generate Project demo data}
{--workhours : Generate WorkHour demo data}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate demo data for SmartdokProfile, Department, Project, and WorkHour models';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// Define arrays for random selection (fill these later)
$profileNames = [
'first_names' => [
"Aksel", "Alma", "Andreas", "Anna", "Arne", "Aurora", "Benedicte", "Benjamin", "Bjørn", "Camilla",
"Cecilie", "Christer", "Dag", "Daniel", "David", "Einar", "Eirik", "Elin", "Emil", "Emilie",
"Erik", "Even", "Fredrik", "Gerd", "Grethe", "Gunnar", "Halvor", "Hanna", "Hans", "Håkon",
"Hege", "Henrik", "Ida", "Ingrid", "Ivar", "Jan", "Jens", "Joakim", "Johanne", "Jonas",
"Jorunn", "Jørgen", "Julie", "Kaja", "Kari", "Karoline", "Kasper", "Katrine", "Kenneth", "Kim",
"Kine", "Kristian", "Kristin", "Kurt", "Lars", "Leif", "Line", "Linn", "Lise", "Liv",
"Magnus", "Maja", "Maren", "Maria", "Marie", "Marit", "Markus", "Marthe", "Martin", "Mathias",
"Mats", "Mette", "Mia", "Mikkel", "Mona", "Nils", "Nina", "Odd", "Ola", "Ole",
"Oskar", "Pål", "Per", "Ragnhild", "Rasmus", "Reidar", "Renate", "Roar", "Robert", "Ronny",
"Rune", "Sander", "Sandra", "Sara", "Signe", "Silje", "Simen", "Siri", "Sofie", "Stian"
],
'last_names' => [
"Andersen", "Antonsen", "Aune", "Bakke", "Berg", "Berge", "Berntsen", "Birkeland", "Blom", "Borge",
"Dahl", "Dale", "Danielsen", "Egeland", "Eggen", "Eide", "Eliassen", "Eriksen", "Evensen", "Fjeld",
"Fjell", "Fredriksen", "Furseth", "Gabrielsen", "Gundersen", "Gustavsen", "Halvorsen", "Hansen", "Haugen", "Hauge",
"Haugland", "Hellesøy", "Hellevik", "Hemmingsen", "Hovland", "Husby", "Iversen", "Jakobsen", "Jensen", "Johannesen",
"Johansen", "Johnsen", "Karlsen", "Knudsen", "Knutsen", "Kristensen", "Kristiansen", "Krogstad", "Kvamme", "Langli",
"Larsen", "Lauvik", "Lie", "Lund", "Magnussen", "Martinsen", "Mathisen", "Mikkelsen", "Moen", "Myhre",
"Nergaard", "Nilsen", "Nordahl", "Nordby", "Næss", "Olsen", "Opdahl", "Pedersen", "Rasmussen", "Rognli",
"Rønning", "Rygh", "Sand", "Sandberg", "Sandnes", "Simonsen", "Sivertsen", "Skogen", "Solbakken", "Solberg",
"Sørensen", "Stavrum", "Stenersen", "Stokke", "Strand", "Strømsø", "Sund", "Sveen", "Sæther", "Sørli",
"Tangen", "Tellefsen", "Thoresen", "Tveit", "Ulriksen", "Vagle", "Vik", "Viken", "Vold", "Ødegård"
],
];
$departmentNames = [
"Økonomiavdelingen", "Personalavdelingen", "HR-avdelingen", "IT-avdelingen", "Driftsavdelingen",
"Kommunikasjonsavdelingen", "Kundeservice", "Forskning og utvikling", "Lønnsavdelingen", "Regnskapsavdelingen",
"Innkjøpsavdelingen", "Juridisk avdeling", "Markedsavdelingen", "Salgsteamet", "Servicedesk", "Supportavdelingen",
"Prosjektavdelingen", "Planavdelingen", "Byggesaksavdelingen", "Teknisk avdeling", "Strategiavdelingen",
"Ledelsessekretariatet", "Analyseavdelingen", "Utdanningsseksjonen", "Arkivavdelingen", "Rekrutteringsavdelingen",
"Brukerstøtte", "Digitaliseringsavdelingen", "Innovasjonsavdelingen", "Logistikkavdelingen", "Miljøavdelingen",
"HMS-avdelingen", "Sikkerhetsavdelingen", "Kvalitetsavdelingen", "Tjenesteutvikling", "Operativ enhet",
"Forvaltningsavdelingen", "Bymiljøavdelingen", "Oppvekstavdelingen", "Barnehageseksjonen", "Skoleseksjonen",
"Teknologi og utvikling", "Næringsavdelingen", "Kulturavdelingen", "Informasjonsavdelingen", "Kommuneadvokaten",
"Internrevisjonen", "Kundeopplevelse", "Datavarehus og innsikt", "Strategi og analyse", "Digital transformasjon",
"Utvendig drift", "Intern IT", "Fagavdelingen", "Avdeling for samfunnskontakt", "Avdeling for prosjektledelse",
"Forretningsutvikling", "Transportavdelingen", "Eiendomsforvaltning", "Reiselivsavdelingen", "Dokumentsenteret",
"Systemforvaltning", "Brannsikkerhet", "HR og organisasjon", "Lønn og personal", "Budsjett og finans",
"Saksbehandlerteam", "Brukeradministrasjon", "Forskningsstøtte", "Utviklingsseksjonen", "Teknisk støtte",
"HR-partnere", "Drift og vedlikehold", "IT-sikkerhet", "Skatteoppfølging", "Kundeavdelingen",
"Infrastruktur og nettverk", "Sosiale medier-teamet", "Reiseadministrasjon", "Journalføring og arkiv",
"Prosessforbedring", "Arbeidsmiljøutvalget", "Produktutvikling", "Digital kommunikasjon", "Revisjonsavdelingen",
"Kundereisen", "Tjenestekatalog", "Fagstab", "Innbyggertorg", "Velferdstjenester", "Beredskap og krisehåndtering",
"Kontor for internasjonalt samarbeid", "Eierskapsavdelingen", "Fellesadministrasjonen", "Boligforvaltning",
"Avdeling for offentlige anskaffelser", "Datateamet", "Digital forvaltning", "Informasjonssikkerhet",
"Strategisk stab", "Kvalitet og forbedring", "Kunnskapsutvikling"
];
$projectNames = [
"Prosjekt Ny Start", "Digital Fremtid", "Kunde360", "Grønn Omstilling", "Effektiv Drift",
"Smart By 2030", "Trygg Skolevei", "Rekrutt 2.0", "Digital Samhandling", "Datadrevet Innsikt",
"Prosjekt Arbeidsglede", "Bærekraft i Fokus", "Felles Plattform", "Modernisering 2025", "Mobil Først",
"HR Next", "Prosjekt Miljøbyen", "Felles HR-system", "Digital Arkivflyt", "Innbyggerdialog 2.0",
"Kontor for Kontor", "Prosjekt Klar Tale", "Trygg og Sikker", "Ny Visuell Profil", "Skytjenester 2026",
"Innovasjonsløftet", "Samspill 2.0", "Prosjekt Klarhet", "Grønn Mobilitet", "StrategiReboot",
"Kvalitet i Tjenester", "Prosjekt Kompetanse", "Smidig Overgang", "Felles IT-struktur", "Innkjøpsløft",
"Smart Skole", "Effektiv Bemanning", "Energi og Effektivitet", "Prosjekt God Morgen", "Digitale Prosesser",
"Veien Videre", "Prosjekt Nye Muligheter", "Framtidens Hjemmetjeneste", "Datadeling i Praksis", "Innbyggerportalen",
"Felles Sakssystem", "Trygg Hverdag", "Ren Digitalisering", "Grønn Strategi", "Prosjekt Ny Giv",
"Et Bedre Arbeidsmiljø", "Prosjekt Enhet", "Digital Tilgjengelighet", "Smart Infrastruktur", "Papirløs 2025",
"Prosjekt Riktig Kompetanse", "Sikkerhet i Fokus", "Kontinuerlig Forbedring", "Prosjekt Klar Oversikt",
"Felles Kommunikasjon", "Miljømål 2030", "Reisen til Skyen", "Tilbake til Kontoret", "Prosjekt Digital Kultur",
"Brukerfokusert Utvikling", "Automatisering Nå", "Egenmeldt Framtid", "Prosjekt Innsikt", "Felles Digitale Flater",
"Plattform24", "Grønn Drift", "Prosjekt Datasikkerhet", "Ny Struktur", "Arbeid 2.0", "Prosjekt Kompetanseløft",
"Strategisk Veikart", "Fremtidens Kontor", "Digital Styring", "Reisefri Samhandling", "Prosjekt Smarte Løsninger",
"Brukeren Først", "Prosjekt Nye Rammer", "Nyskapning i Nord", "Helhetlig Tjenestereise", "Grønt Innkjøp",
"DataHub Kommunal", "Lønn og Rolle", "Digital Døgnåpen", "Prosjekt Kontinuitet", "Klima og Energi",
"Sikker Jobbhverdag", "Smart Senterstruktur", "Ny HR-portal", "Kundereise i Fokus", "Automatiseringsløftet",
"Tjenestedesign 2025", "Effektiv Vedlikehold", "Nye Arbeidsformer", "Avviksfri Drift", "Innsyn og Åpenhet",
"Framtidens Elevtjeneste", "Strategisk Sikkerhet", "Felles Admin", "Velferd på Nett", "Kunnskapsløft Intern"
];
$workHourComments = [
"Utviklet nye funksjoner i løsningen for Prosjekt 1,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Hjalp til med forberedelser til workshop for Prosjekt 2. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Deltok i brukertest for Prosjekt 3,
der vi samlet tilbakemeldinger fra interne brukere. Dokumenterte funn og oppdaterte kravspesifikasjonen i henhold til det som kom fram. Viktig del av iterativ utvikling.",
"Gjennomførte kvalitetssikring av eksisterende leveranser i Prosjekt 4. Gikk gjennom dokumentasjon,
sjekket etter avvik og laget en oppsummeringsrapport med anbefalinger. Dette gir bedre struktur og oversikt fremover.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 5,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 6,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Utviklet nye funksjoner i løsningen for Prosjekt 7,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Oppdaterte prosjektstyringsverktøy for Prosjekt 8 etter interne endringer. Loggførte aktiviteter,
justerte tidslinjer og la til kommentarer for tydeligere historikk. Dette gir teamet bedre oversikt over fremdriften.",
"Utviklet nye funksjoner i løsningen for Prosjekt 9,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Deltok i brukertest for Prosjekt 10,
der vi samlet tilbakemeldinger fra interne brukere. Dokumenterte funn og oppdaterte kravspesifikasjonen i henhold til det som kom fram. Viktig del av iterativ utvikling.",
"Gjennomførte kvalitetssikring av eksisterende leveranser i Prosjekt 11. Gikk gjennom dokumentasjon,
sjekket etter avvik og laget en oppsummeringsrapport med anbefalinger. Dette gir bedre struktur og oversikt fremover.",
"Oppdaterte prosjektstyringsverktøy for Prosjekt 12 etter interne endringer. Loggførte aktiviteter,
justerte tidslinjer og la til kommentarer for tydeligere historikk. Dette gir teamet bedre oversikt over fremdriften.",
"Deltok i brukertest for Prosjekt 13,
der vi samlet tilbakemeldinger fra interne brukere. Dokumenterte funn og oppdaterte kravspesifikasjonen i henhold til det som kom fram. Viktig del av iterativ utvikling.",
"Gjennomførte kvalitetssikring av eksisterende leveranser i Prosjekt 14. Gikk gjennom dokumentasjon,
sjekket etter avvik og laget en oppsummeringsrapport med anbefalinger. Dette gir bedre struktur og oversikt fremover.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 15,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 16,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Deltok i møte med prosjektgruppen for Prosjekt 17,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Deltok i brukertest for Prosjekt 18,
der vi samlet tilbakemeldinger fra interne brukere. Dokumenterte funn og oppdaterte kravspesifikasjonen i henhold til det som kom fram. Viktig del av iterativ utvikling.",
"Deltok i brukertest for Prosjekt 19,
der vi samlet tilbakemeldinger fra interne brukere. Dokumenterte funn og oppdaterte kravspesifikasjonen i henhold til det som kom fram. Viktig del av iterativ utvikling.",
"Skrev referat fra prosjektmøte i Prosjekt 20. Oppsummerte beslutninger,
fordelte oppgaver og lastet opp til felles dokumentbibliotek. Sendte ut e-post til alle involverte med påminnelser og neste møtetidspunkt.",
"Utviklet nye funksjoner i løsningen for Prosjekt 21,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Gjennomførte kvalitetssikring av eksisterende leveranser i Prosjekt 22. Gikk gjennom dokumentasjon,
sjekket etter avvik og laget en oppsummeringsrapport med anbefalinger. Dette gir bedre struktur og oversikt fremover.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 23,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Skrev referat fra prosjektmøte i Prosjekt 24. Oppsummerte beslutninger,
fordelte oppgaver og lastet opp til felles dokumentbibliotek. Sendte ut e-post til alle involverte med påminnelser og neste møtetidspunkt.",
"Oppdaterte prosjektstyringsverktøy for Prosjekt 25 etter interne endringer. Loggførte aktiviteter,
justerte tidslinjer og la til kommentarer for tydeligere historikk. Dette gir teamet bedre oversikt over fremdriften.",
"Utviklet nye funksjoner i løsningen for Prosjekt 26,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 27,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 28,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Deltok i møte med prosjektgruppen for Prosjekt 29,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 30,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Arbeidet med analyse og innsiktsinnsamling for Prosjekt 31. Brukte tid på å hente ut data,
kvalitetssikre informasjon og lage et første utkast til rapport. Resultatene vil bidra til bedre beslutningsgrunnlag videre i prosessen.",
"Deltok i møte med prosjektgruppen for Prosjekt 32,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 33,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Deltok i brukertest for Prosjekt 34,
der vi samlet tilbakemeldinger fra interne brukere. Dokumenterte funn og oppdaterte kravspesifikasjonen i henhold til det som kom fram. Viktig del av iterativ utvikling.",
"Deltok i møte med prosjektgruppen for Prosjekt 35,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 36,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Deltok i møte med prosjektgruppen for Prosjekt 37,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Arbeidet med analyse og innsiktsinnsamling for Prosjekt 38. Brukte tid på å hente ut data,
kvalitetssikre informasjon og lage et første utkast til rapport. Resultatene vil bidra til bedre beslutningsgrunnlag videre i prosessen.",
"Skrev referat fra prosjektmøte i Prosjekt 39. Oppsummerte beslutninger,
fordelte oppgaver og lastet opp til felles dokumentbibliotek. Sendte ut e-post til alle involverte med påminnelser og neste møtetidspunkt.",
"Deltok i brukertest for Prosjekt 40,
der vi samlet tilbakemeldinger fra interne brukere. Dokumenterte funn og oppdaterte kravspesifikasjonen i henhold til det som kom fram. Viktig del av iterativ utvikling.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 41,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 42,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 43,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 44,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 45,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Utviklet nye funksjoner i løsningen for Prosjekt 46,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Skrev referat fra prosjektmøte i Prosjekt 47. Oppsummerte beslutninger,
fordelte oppgaver og lastet opp til felles dokumentbibliotek. Sendte ut e-post til alle involverte med påminnelser og neste møtetidspunkt.",
"Hjalp til med forberedelser til workshop for Prosjekt 48. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Utviklet nye funksjoner i løsningen for Prosjekt 49,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Oppdaterte prosjektstyringsverktøy for Prosjekt 50 etter interne endringer. Loggførte aktiviteter,
justerte tidslinjer og la til kommentarer for tydeligere historikk. Dette gir teamet bedre oversikt over fremdriften.",
"Arbeidet med analyse og innsiktsinnsamling for Prosjekt 51. Brukte tid på å hente ut data,
kvalitetssikre informasjon og lage et første utkast til rapport. Resultatene vil bidra til bedre beslutningsgrunnlag videre i prosessen.",
"Hjalp til med forberedelser til workshop for Prosjekt 52. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Hjalp til med forberedelser til workshop for Prosjekt 53. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Hjalp til med forberedelser til workshop for Prosjekt 54. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Deltok i møte med prosjektgruppen for Prosjekt 55,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Skrev referat fra prosjektmøte i Prosjekt 56. Oppsummerte beslutninger,
fordelte oppgaver og lastet opp til felles dokumentbibliotek. Sendte ut e-post til alle involverte med påminnelser og neste møtetidspunkt.",
"Arbeidet med analyse og innsiktsinnsamling for Prosjekt 57. Brukte tid på å hente ut data,
kvalitetssikre informasjon og lage et første utkast til rapport. Resultatene vil bidra til bedre beslutningsgrunnlag videre i prosessen.",
"Hjalp til med forberedelser til workshop for Prosjekt 58. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Deltok i møte med prosjektgruppen for Prosjekt 59,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 60,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 61,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Gjennomførte kvalitetssikring av eksisterende leveranser i Prosjekt 62. Gikk gjennom dokumentasjon,
sjekket etter avvik og laget en oppsummeringsrapport med anbefalinger. Dette gir bedre struktur og oversikt fremover.",
"Deltok i brukertest for Prosjekt 63,
der vi samlet tilbakemeldinger fra interne brukere. Dokumenterte funn og oppdaterte kravspesifikasjonen i henhold til det som kom fram. Viktig del av iterativ utvikling.",
"Hjalp til med forberedelser til workshop for Prosjekt 64. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 65,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 66,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Oppdaterte prosjektstyringsverktøy for Prosjekt 67 etter interne endringer. Loggførte aktiviteter,
justerte tidslinjer og la til kommentarer for tydeligere historikk. Dette gir teamet bedre oversikt over fremdriften.",
"Arbeidet med analyse og innsiktsinnsamling for Prosjekt 68. Brukte tid på å hente ut data,
kvalitetssikre informasjon og lage et første utkast til rapport. Resultatene vil bidra til bedre beslutningsgrunnlag videre i prosessen.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 69,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 70,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Utviklet nye funksjoner i løsningen for Prosjekt 71,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 72,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Utviklet nye funksjoner i løsningen for Prosjekt 73,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 74,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Deltok i møte med prosjektgruppen for Prosjekt 75,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 76,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 77,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Deltok i møte med prosjektgruppen for Prosjekt 78,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Utviklet nye funksjoner i løsningen for Prosjekt 79,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Oppdaterte prosjektstyringsverktøy for Prosjekt 80 etter interne endringer. Loggførte aktiviteter,
justerte tidslinjer og la til kommentarer for tydeligere historikk. Dette gir teamet bedre oversikt over fremdriften.",
"Deltok i møte med prosjektgruppen for Prosjekt 81,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 82,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 83,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Gjennomførte kvalitetssikring av eksisterende leveranser i Prosjekt 84. Gikk gjennom dokumentasjon,
sjekket etter avvik og laget en oppsummeringsrapport med anbefalinger. Dette gir bedre struktur og oversikt fremover.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 85,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Utviklet nye funksjoner i løsningen for Prosjekt 86,
inkludert testing og dokumentasjon. Samarbeidet tett med utviklingsteamet for å løse oppdagede bugs og sikre en stabil leveranse til neste sprint.",
"Arbeidet med analyse og innsiktsinnsamling for Prosjekt 87. Brukte tid på å hente ut data,
kvalitetssikre informasjon og lage et første utkast til rapport. Resultatene vil bidra til bedre beslutningsgrunnlag videre i prosessen.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 88,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Arbeidet med analyse og innsiktsinnsamling for Prosjekt 89. Brukte tid på å hente ut data,
kvalitetssikre informasjon og lage et første utkast til rapport. Resultatene vil bidra til bedre beslutningsgrunnlag videre i prosessen.",
"Hjalp til med forberedelser til workshop for Prosjekt 90. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Skrev referat fra prosjektmøte i Prosjekt 91. Oppsummerte beslutninger,
fordelte oppgaver og lastet opp til felles dokumentbibliotek. Sendte ut e-post til alle involverte med påminnelser og neste møtetidspunkt.",
"Gjennomførte kvalitetssikring av eksisterende leveranser i Prosjekt 92. Gikk gjennom dokumentasjon,
sjekket etter avvik og laget en oppsummeringsrapport med anbefalinger. Dette gir bedre struktur og oversikt fremover.",
"Arbeidet med analyse og innsiktsinnsamling for Prosjekt 93. Brukte tid på å hente ut data,
kvalitetssikre informasjon og lage et første utkast til rapport. Resultatene vil bidra til bedre beslutningsgrunnlag videre i prosessen.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 94,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Deltok i møte med prosjektgruppen for Prosjekt 95,
hvor vi diskuterte fremdriften og identifiserte eventuelle flaskehalser. Gjennomgikk dokumentasjon og oppdaterte prosjektplaner for å sikre at alle involverte er informert.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 96,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
"Gjorde teknisk oppsett i utviklingsmiljøet for Prosjekt 97,
inkludert konfigurering av servere og deploy-rutiner. Brukte god tid på testing og feilretting for å sikre at systemet fungerer som forventet.",
"Hjalp til med forberedelser til workshop for Prosjekt 98. Laget presentasjonsmateriell,
organiserte agenda og koordinerte med eksterne bidragsytere. Fokuset var å sikre god involvering og innsikt fra deltakerne.",
"Gjennomførte kvalitetssikring av eksisterende leveranser i Prosjekt 99. Gikk gjennom dokumentasjon,
sjekket etter avvik og laget en oppsummeringsrapport med anbefalinger. Dette gir bedre struktur og oversikt fremover.",
"Gjennomførte presentasjon for ledelsen om status i Prosjekt 100,
inkludert nøkkeltall,
risikoer og anbefalinger. Fikk gode tilbakemeldinger og justerte fokus for neste uke basert på innspillene.",
];
// Generate SmartdokProfile data
if ($this->option('profiles')) {
$count = (int) $this->ask('How many SmartdokProfile records do you want to create? (1-1000)');
if ($count < 1 || $count > 1000) {
$this->error('Please enter a number between 1 and 1000.');
return 1;
}
for ($i = 0; $i < $count; $i++) {
$first = Arr::random($profileNames['first_names']);
$last = Arr::random($profileNames['last_names']);
$username = Str::slug(strtolower("{$first}.{$last}"));
SmartdokProfile::create([
'id' => (string) Str::uuid(),
'username' => $username,
'name' => "{$first} {$last}",
]);
}
$this->info("Successfully created {$count} SmartdokProfile records.");
}
// Generate Department data
if ($this->option('departments')) {
$count = (int) $this->ask('How many Department records do you want to create? (1-1000)');
if ($count < 1 || $count > 1000) {
$this->error('Please enter a number between 1 and 1000.');
return 1;
}
for ($i = 0; $i < $count; $i++) {
Department::create([
'name' => Arr::random($departmentNames),
]);
}
$this->info("Successfully created {$count} Department records.");
}
// Generate Project data (requires at least one profile and one department)
if ($this->option('projects')) {
if (SmartdokProfile::count() === 0 || Department::count() === 0) {
$this->error('Cannot create projects: please ensure at least one SmartdokProfile and one Department exist.');
return 1;
}
$i = 1;
foreach ($projectNames as $name) {
$project = Project::create([
'project_name' => $name,
'project_number' => (string) rand(1000, 9999),
]);
// Attach random profiles and departments
$profileIds = SmartdokProfile::inRandomOrder()->take(rand(1, 3))->pluck('id')->toArray();
$departmentIds = Department::inRandomOrder()->take(rand(1, 3))->pluck('id')->toArray();
$project->profiles()->attach($profileIds);
$project->departments()->attach($departmentIds);
$i++;
}
$this->info("Successfully created {$i} Project records (with random attachments).");
}
// Generate WorkHour data (requires at least one profile and one project)
if ($this->option('workhours')) {
if (SmartdokProfile::count() === 0 || Project::count() === 0) {
$this->error('Cannot create work hours: please ensure at least one SmartdokProfile and one Project exist.');
return 1;
}
$count = (int) $this->ask('How many WorkHour records do you want to create? (1-1000)');
if ($count < 1 || $count > 1000) {
$this->error('Please enter a number between 1 and 1000.');
return 1;
}
$startInput = $this->ask('Enter start date (dd/mm/YYYY)');
$endInput = $this->ask('Enter end date (dd/mm/YYYY)');
try {
$start = Carbon::createFromFormat('d/m/Y', $startInput)->startOfDay();
$end = Carbon::createFromFormat('d/m/Y', $endInput)->endOfDay();
} catch (\Exception $e) {
$this->error('Invalid date format. Please use dd/mm/YYYY.');
return 1;
}
for ($i = 0; $i < $count; $i++) {
$userId = SmartdokProfile::inRandomOrder()->first()->id;
$projectId = Project::inRandomOrder()->first()->id;
$ordinaryHours = rand(1, 8);
$comment = Arr::random($workHourComments);
// Pick a random timestamp between start and end
$timestamp = rand($start->timestamp, $end->timestamp);
$workDate = Carbon::createFromTimestamp($timestamp)->format('Y-m-d H:i:s');
WorkHour::create([
'user_id' => $userId,
'project_id' => $projectId,
'ordinary_hours'=> $ordinaryHours,
'work_date' => $workDate,
'comment' => $comment,
]);
}
$this->info("Successfully created {$count} WorkHour records.");
}
return 0;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Carbon\Carbon;
class mkuser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:mkuser';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new user interactively';
/**
* Execute the console command.
*/
public function handle()
{
// Gather user information
$name = $this->ask('Enter the user name');
$email = $this->ask('Enter the user email');
$password = $this->secret('Enter the user password');
// Create the user
$user = User::create([
'name' => $name,
'email' => $email,
'password' => Hash::make($password),
'email_verified_at' => Carbon::now(),
]);
// Output success message
$this->info("User '{$user->name}' ({$user->email}) created successfully with ID {$user->id}.");
return 0;
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\ApiAdmins;
class RmApiAdmin extends Command
{
protected $signature = 'rmapiadmin';
protected $description = 'List users with an ApiAdmins record and remove the relationship';
public function handle() {
$users = User::has('apiAdmin')
->orderBy('id')
->get(['id', 'name', 'email']);
if ($users->isEmpty()) {
$this->info('No users currently assigned as API Admin.');
return 0;
}
$choices = $users->map(fn($u) => "{$u->id}: {$u->name} ({$u->email})")->toArray();
$selected = $this->choice(
'Select a user to remove from API Admins',
$choices,
0,
null,
false
);
[$idString] = explode(':', $selected, 2);
$userId = (int) trim($idString);
ApiAdmins::where('user_id', $userId)->delete();
$this->info("User ID {$userId} has been removed from API Admins.");
return 0;
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use App\Models\SmartdokProfile;
use App\Models\Department;
use App\Models\Project;
use App\Models\WorkHour;
class RmDemoData extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'rmdemodata';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Remove all demo data for SmartdokProfile, Department, Project, and WorkHour models';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->info('Removing all demo data...');
// Disable foreign key checks for all supported DB drivers
Schema::disableForeignKeyConstraints();
// Truncate pivot tables
DB::table('project_smartdok_profile')->truncate();
DB::table('department_project')->truncate();
// Truncate core tables
WorkHour::truncate();
Project::truncate();
SmartdokProfile::truncate();
Department::truncate();
// Re-enable foreign key checks
Schema::enableForeignKeyConstraints();
$this->info('All demo data removed successfully.');
return 0;
}
}
@@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Response;
use Illuminate\Http\Request;
class AuthenticationController extends Controller
{
/**
* @unauthenticated
*
* @return "{'status': 'success','token': 'plaintexttoken'}"
*
*
*/
public function authorize(Request $request) {
$request->validate([
'email' => 'required|email',
'password' => 'required|string'
]);
if (!$request->has('email') || !$request->has('password')) {
return json_encode([
'status' => 'invalid',
'message' => 'ingen brukernavn eller passord oppgitt'
], 422);
}
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
$user = Auth::user();
$token = $user->createToken($request->token_name ?? 'default');
return json_encode([
'status' => 'success',
'token' => $token->plainTextToken
], Response::HTTP_ACCEPTED);
}
return json_encode([
'status' => 'invalid',
'message' => 'ugyldig bruker/passord'
], Response::HTTP_UNAUTHORIZED);
}
}
@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Models\Department;
class DepartmentController extends Controller
{
/**
* GET /Departments
* Query params:
* - departmentName (string)
* - updatedSince (date-time)
*/
public function index(Request $request) {
$q = Department::query();
if ($name = $request->query('departmentName')) {
$q->where('name', $name);
}
if ($since = $request->query('updatedSince')) {
$q->where('updated_at', '>=', Carbon::parse($since));
}
$out = $q->get()->map(function ($d) {
return [
'Id' => $d->id,
'Name' => $d->name,
'Updated' => $d->updated_at->toIso8601String().'Z',
];
});
return response()->json($out);
}
/**
* GET /Departments/{id}
*/
public function show(int $id) {
$d = Department::findOrFail($id);
return response()->json([
'Id' => $d->id,
'Name' => $d->name,
'Updated' => $d->updated_at->toIso8601String().'Z',
]);
}
}
@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Models\Project;
class ProjectController extends Controller
{
/**
* GET /Projects
* Query params:
* - projectNumber (string)
* - projectName (string)
* - updatedSince (date-time)
* - createdSince (date-time)
*/
public function index(Request $request) {
$q = Project::with(['departments', 'profiles']);
if ($num = $request->query('projectNumber')) {
$q->where('project_number', $num);
}
if ($name = $request->query('projectName')) {
$q->where('project_name', $name);
}
if ($sinceUp = $request->query('updatedSince')) {
$q->where('updated_at', '>=', Carbon::parse($sinceUp));
}
if ($sinceCr = $request->query('createdSince')) {
$q->where('created_at', '>=', Carbon::parse($sinceCr));
}
$out = $q->get()->map(function ($p) {
return [
'Id' => $p->id,
'ProjectName' => $p->project_name,
'ProjectNumber' => $p->project_number,
'Departments' => $p->departments->pluck('id')->all(),
'UserIds' => $p->profiles->pluck('id')->all(),
'Updated' => $p->updated_at->toIso8601String().'Z',
'Created' => $p->created_at->toIso8601String().'Z',
];
});
return response()->json($out);
}
/**
* GET /Projects/{id}
*/
public function show(int $id) {
$p = Project::with(['departments', 'profiles'])->findOrFail($id);
return response()->json([
'Id' => $p->id,
'ProjectName' => $p->project_name,
'ProjectNumber' => $p->project_number,
'Departments' => $p->departments->pluck('id')->all(),
'UserIds' => $p->profiles->pluck('id')->all(),
'Updated' => $p->updated_at->toIso8601String().'Z',
'Created' => $p->created_at->toIso8601String().'Z',
]);
}
}
@@ -0,0 +1,140 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Models\ApiKeys;
class SmartdokLiveController extends Controller
{
private string $serviceName = 'smartdok';
private string $apiKey;
private string $authToken;
private string $serviceUrl = 'https://api.smartdok.no';
public function __construct() {
$record = ApiKeys::where('category', $this->serviceName)->first();
if (! $record) {
throw new HttpResponseException(
response()->json([
'feil' => "ingen tjenesteoppføring funnet for tjenesten '{$this->serviceName}', som betyr at du ikke har definert en API nøkkel for denne tjenesten enda. Logg inn og legg til en API nøkkel først"
], 404)
);
}
$this->apiKey = $record->key;
$smartdokAuthorizeRequest = Http::withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json'
])->post("{$this->serviceUrl}/Authorize/ApiToken", [
'input' => $this->apiKey
]);
$response = $smartdokAuthorizeRequest->json();
if (!isset($response['Token'])) {
throw new HttpResponseException(
response()->json([
'feil' => "Kunne ikke hente Visma SmartDok autorisasjon ved bruk av den lagrede API nøkkelen i mellomvaren"
], 403)
);
}
$this->authToken = $response['Token'];
}
public function users() {
$smartdokUsersRequest = Http::withHeaders([
'Authorization' => "Bearer {$this->authToken}",
'Accept' => 'application/json'
])->get("{$this->serviceUrl}/Users");
return $smartdokUsersRequest->json();
}
public function user(string $id) {
$smartdokUsersRequest = Http::withHeaders([
'Authorization' => "Bearer {$this->authToken}",
'Accept' => 'application/json'
])->get("{$this->serviceUrl}/Users/{$id}");
return $smartdokUsersRequest->json();
}
public function departments() {
$smartdokDepartmentsRequest = Http::withHeaders([
'Authorization' => "Bearer {$this->authToken}",
'Accept' => 'application/json'
])->get("{$this->serviceUrl}/Departments");
return $smartdokDepartmentsRequest->json();
}
public function department(string $id) {
$smartdokDepartmentsRequest = Http::withHeaders([
'Authorization' => "Bearer {$this->authToken}",
'Accept' => 'application/json'
])->get("{$this->serviceUrl}/Departments/{$id}");
return $smartdokDepartmentsRequest->json();
}
public function projects() {
$smartdokProjectsRequest = Http::withHeaders([
'Authorization' => "Bearer {$this->authToken}",
'Accept' => 'application/json'
])->get("{$this->serviceUrl}/Projects");
return $smartdokProjectsRequest->json();
}
public function project(string $id) {
$smartdokProjectsRequest = Http::withHeaders([
'Authorization' => "Bearer {$this->authToken}",
'Accept' => 'application/json'
])->get("{$this->serviceUrl}/Projects/{$id}");
return $smartdokProjectsRequest->json();
}
public function WorkHours(Request $request) {
$request->validate([
'fromDate' => 'required|date',
'toDate' => 'required|date',
'projectId' => 'nullable|integer',
]);
$from = Carbon::parse($request->query('fromDate'))
->startOfDay()
->toDateTimeString(); // “2025-05-12 00:00:00”
$to = Carbon::parse($request->query('toDate'))
->endOfDay()
->toDateTimeString(); // “2025-05-12 23:59:59”
$query = [
'fromDate' => $from,
'toDate' => $to,
];
if (! is_null($request->input('projectId'))) {
$query['projectId'] = $request->input('projectId');
}
$smartdokWorkHourRequest = Http::withHeaders([
'Authorization' => "Bearer {$this->authToken}",
'Accept' => 'application/json'
])->get("{$this->serviceUrl}/WorkHours", $query);
return $smartdokWorkHourRequest->json();
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\SmartdokProfile;
class SmartdokProfileController extends Controller
{
/**
* List all users
*
* Returns a JSON array of all the smartdok users registered on the system
*
* @response \App\Models\SmartdokProfile
*/
public function index() {
$profiles = SmartdokProfile::all()->map(function ($p) {
return [
'Id' => $p->id,
'UserName' => $p->username,
'Name' => $p->name,
];
});
return response()->json($profiles);
}
/**
* Get a user
*
* Returns a JSON array of all the smartdok users registered on the system
*
* @param $id
*
* Example response:
* {
* "Id": "00000000-0000-0000-0000-000000000000",
* "UserName": "string",
* "Name": "string"
* }
*/
public function show(string $id) {
$p = SmartdokProfile::findOrFail($id);
return response()->json([
'Id' => $p->id,
'UserName' => $p->username,
'Name' => $p->name,
]);
}
}
@@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Models\WorkHour;
class WorkHourController extends Controller
{
/**
* GET /WorkHours
*
* Required query params:
* - fromDate : date-time (filters work_date >= fromDate)
* - toDate : date-time (filters work_date <= toDate)
* Optional query param:
* - projectId : integer (filters by associated project)
*
* @response \App\Models\WorkHour
*/
public function index(Request $request) {
$request->validate([
'fromDate' => 'required|date',
'toDate' => 'required|date',
'projectId' => 'nullable|integer',
]);
$from = Carbon::parse($request->query('fromDate'))
->startOfDay()
->toDateTimeString(); // “2025-05-12 00:00:00”
$to = Carbon::parse($request->query('toDate'))
->endOfDay()
->toDateTimeString(); // “2025-05-12 23:59:59”
$q = WorkHour::with('project.departments')
->whereBetween('work_date', [$from, $to]);
if ($pid = $request->query('projectId')) {
// filter directly on the foreign key
$q->where('project_id', $pid);
}
$result = $q->get()->map(function (WorkHour $w) {
$deptIds = $w->project
->departments
->pluck('id')
->unique()
->values()
->all();
return [
'WorkHourID' => $w->id,
'UserID' => $w->user_id,
'ProjectID' => $w->project_id,
'OrdinaryHours' => $w->ordinary_hours,
'Date' => $w->work_date->toDateString(),
'Comment' => $w->comment,
'DepartmentIDsProject' => $deptIds,
'Updated' => $w->updated_at->toIso8601String() . 'Z',
'Created' => $w->created_at->toIso8601String() . 'Z',
];
});
return response()->json($result);
}
}
@@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\ApiAdmins;
use App\Models\ApiKeys;
class ApiAdminController extends Controller
{
public function index(Request $request) {
$userId = Auth::id();
$isAdmin = ApiAdmins::where('user_id', $userId)->exists();
$categories = Apikeys::select('id', 'category')
->orderBy('category')
->get();
return Inertia::render('Dashboard', [
'isAdmin' => $isAdmin,
'categories' => $categories,
]);
}
public function create() {
$userId = Auth::id();
$isAdmin = ApiAdmins::where('user_id', $userId)->exists();
if (! $isAdmin) {
abort(403);
}
$raw = config('api_key_categories', []);
$categories = collect($raw)
->map(fn($desc, $key) => ['key' => $key, 'description' => $desc])
->values()
->all();
return Inertia::render('ApiKeys/Add', [
'categories' => $categories,
]);
}
public function store(Request $request) {
$userId = Auth::id();
$isAdmin = ApiAdmins::where('user_id', $userId)->exists();
if (! $isAdmin) {
abort(403);
}
$data = $request->validate([
'category' => 'required|string|unique:apikeys,category',
'key' => 'required|string',
]);
Apikeys::create([
'category' => $data['category'],
'key' => $data['key'],
]);
return redirect()
->route('dashboard')
->with('success', 'API‐key category added.');
}
public function destroy(int $id) {
$userId = Auth::id();
$isAdmin = ApiAdmins::where('user_id', $userId)->exists();
if (! $isAdmin) {
abort(403);
}
// Attempt to find the record—404 if not found
$apiKey = Apikeys::findOrFail($id);
$apiKey->delete();
return redirect()
->route('dashboard')
->with('success', 'API‐key category removed.');
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use Inertia\Response;
class AuthenticatedSessionController extends Controller
{
/**
* Show the login page.
*/
public function create(Request $request): Response
{
return Inertia::render('auth/Login', [
'canResetPassword' => Route::has('password.request'),
'status' => $request->session()->get('status'),
]);
}
/**
* Handle an incoming authentication request.
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(route('dashboard', absolute: false));
}
/**
* Destroy an authenticated session.
*/
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password page.
*/
public function show(): Response
{
return Inertia::render('auth/ConfirmPassword');
}
/**
* Confirm the user's password.
*/
public function store(Request $request): RedirectResponse
{
if (! Auth::guard('web')->validate([
'email' => $request->user()->email,
'password' => $request->password,
])) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
]);
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(route('dashboard', absolute: false));
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*/
public function store(Request $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false));
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class EmailVerificationPromptController extends Controller
{
/**
* Show the email verification prompt page.
*/
public function __invoke(Request $request): RedirectResponse|Response
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(route('dashboard', absolute: false))
: Inertia::render('auth/VerifyEmail', ['status' => $request->session()->get('status')]);
}
}
@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
class NewPasswordController extends Controller
{
/**
* Show the password reset page.
*/
public function create(Request $request): Response
{
return Inertia::render('auth/ResetPassword', [
'email' => $request->email,
'token' => $request->route('token'),
]);
}
/**
* Handle an incoming new password request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
if ($status == Password::PasswordReset) {
return to_route('login')->with('status', __($status));
}
throw ValidationException::withMessages([
'email' => [__($status)],
]);
}
}
@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Inertia\Inertia;
use Inertia\Response;
class PasswordResetLinkController extends Controller
{
/**
* Show the password reset link request page.
*/
public function create(Request $request): Response
{
return Inertia::render('auth/ForgotPassword', [
'status' => $request->session()->get('status'),
]);
}
/**
* Handle an incoming password reset link request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'email' => 'required|email',
]);
Password::sendResetLink(
$request->only('email')
);
return back()->with('status', __('A reset link will be sent if the account exists.'));
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Inertia\Inertia;
use Inertia\Response;
class RegisteredUserController extends Controller
{
/**
* Show the registration page.
*/
public function create(): Response
{
return Inertia::render('auth/Register');
}
/**
* Handle an incoming registration request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
event(new Registered($user));
Auth::login($user);
return to_route('dashboard');
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\RedirectResponse;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*/
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
/** @var \Illuminate\Contracts\Auth\MustVerifyEmail $user */
$user = $request->user();
event(new Verified($user));
}
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}
@@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Inertia\Inertia;
use App\Models\Department;
class DepartmentController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request) {
$departments = Department::orderBy('name')
->paginate(10)
->through(fn($dept) => [
'id' => $dept->id,
'name' => $dept->name,
]);
return Inertia::render('Department/Index', [
'departments' => $departments,
'filters' => $request->only(['page']),
]);
}
/**
* Show the form for creating a new resource.
*/
public function create() {
return Inertia::render('Department/Create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request) {
$data = $request->validate([
'name' => 'required|string|max:255',
]);
Department::create($data);
return redirect()
->route('smartdok.departments.index')
->with('success', 'Department created successfully.');
}
/**
* Display the specified resource.
*/
public function show(string $id) {
$department = Department::findOrFail($id);
return Inertia::render('Department/Show', [
'department' => [
'id' => $department->id,
'name' => $department->name,
],
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id) {
$department = Department::findOrFail($id);
return Inertia::render('Department/Edit', [
'department' => [
'id' => $department->id,
'name' => $department->name,
],
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id) {
$department = Department::findOrFail($id);
$data = $request->validate([
'name' => 'required|string|max:255',
]);
$department->update($data);
return redirect()
->route('smartdok.departments.index')
->with('success', 'Department updated successfully.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id) {
$department = Department::findOrFail($id);
$department->delete();
return redirect()
->route('smartdok.departments.index')
->with('success', 'Department deleted successfully.');
}
}
+171
View File
@@ -0,0 +1,171 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Project;
use App\Models\SmartdokProfile;
use App\Models\Department;
class ProjectController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request) {
$projects = Project::with(['profiles', 'departments'])
->orderBy('project_name')
->paginate(10)
->through(fn($project) => [
'id' => $project->id,
'project_name' => $project->project_name,
'project_number' => $project->project_number,
'user_ids' => $project->profiles->pluck('id'),
'department_ids' => $project->departments->pluck('id'),
]);
return Inertia::render('Project/Index', [
'projects' => $projects,
'filters' => $request->only(['page']),
]);
}
/**
* Show the form for creating a new resource.
*/
public function create() {
$profiles = SmartdokProfile::orderBy('name')
->get()
->map(fn($p) => ['id' => $p->id, 'name' => $p->name]);
$departments = Department::orderBy('name')
->get()
->map(fn($d) => ['id' => $d->id, 'name' => $d->name]);
return Inertia::render('Project/Create', [
'profiles' => $profiles,
'departments' => $departments,
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request) {
$data = $request->validate([
'project_name' => 'required|string|max:255',
'project_number' => 'required|string|max:255|unique:projects,project_number',
'user_ids' => 'sometimes|array',
'user_ids.*' => 'uuid|exists:smartdok_profiles,id',
'department_ids' => 'sometimes|array',
'department_ids.*' => 'integer|exists:departments,id',
]);
$project = Project::create([
'project_name' => $data['project_name'],
'project_number' => $data['project_number'],
]);
$project->profiles()->sync($data['user_ids'] ?? []);
$project->departments()->sync($data['department_ids'] ?? []);
return redirect()
->route('smartdok.projects.index')
->with('success', 'Project created successfully.');
}
/**
* Display the specified resource.
*/
public function show(string $id) {
$project = Project::with(['profiles', 'departments'])->findOrFail($id);
return Inertia::render('Project/Show', [
'project' => [
'id' => $project->id,
'project_name' => $project->project_name,
'project_number' => $project->project_number,
'profiles' => $project->profiles->map(fn ($p) => [
'id' => $p->id,
'username' => $p->username,
'name' => $p->name,
]),
'departments' => $project->departments->map(fn ($d) => [
'id' => $d->id,
'name' => $d->name,
]),
],
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id) {
$project = Project::with(['profiles', 'departments'])
->findOrFail($id);
$profiles = SmartdokProfile::orderBy('name')
->get()
->map(fn($p) => ['id' => $p->id, 'name' => $p->name]);
$departments = Department::orderBy('name')
->get()
->map(fn($d) => ['id' => $d->id, 'name' => $d->name]);
return Inertia::render('Project/Edit', [
'project' => [
'id' => $project->id,
'project_name' => $project->project_name,
'project_number' => $project->project_number,
// ensure these are plain arrays, not Collections
'user_ids' => $project->profiles->pluck('id')->all(),
'department_ids' => $project->departments->pluck('id')->all(),
],
'profiles' => $profiles,
'departments' => $departments,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id) {
$project = Project::findOrFail($id);
$data = $request->validate([
'project_name' => 'required|string|max:255',
'project_number' => 'required|string|max:255|unique:projects,project_number,' . $project->id,
'user_ids' => 'sometimes|array',
'user_ids.*' => 'uuid|exists:smartdok_profiles,id',
'department_ids' => 'sometimes|array',
'department_ids.*' => 'integer|exists:departments,id',
]);
$project->update([
'project_name' => $data['project_name'],
'project_number' => $data['project_number'],
]);
$project->profiles()->sync($data['user_ids'] ?? []);
$project->departments()->sync($data['department_ids'] ?? []);
return redirect()
->route('smartdok.projects.index')
->with('success', 'Project updated successfully.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id) {
$project = Project::findOrFail($id);
$project->delete();
return redirect()
->route('smartdok.projects.index')
->with('success', 'Project deleted successfully.');
}
}
@@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use Inertia\Inertia;
use Inertia\Response;
class PasswordController extends Controller
{
/**
* Show the user's password settings page.
*/
public function edit(): Response
{
return Inertia::render('settings/Password');
}
/**
* Update the user's password.
*/
public function update(Request $request): RedirectResponse
{
$validated = $request->validate([
'current_password' => ['required', 'current_password'],
'password' => ['required', Password::defaults(), 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
return back();
}
}
@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\Settings\ProfileUpdateRequest;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Inertia\Response;
class ProfileController extends Controller
{
/**
* Show the user's profile settings page.
*/
public function edit(Request $request): Response
{
return Inertia::render('settings/Profile', [
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
'status' => $request->session()->get('status'),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return to_route('profile.edit');
}
/**
* Delete the user's profile.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validate([
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
@@ -0,0 +1,116 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Inertia\Inertia;
use App\Models\SmartdokProfile;
class SmartdokProfileController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request) {
$profiles = SmartdokProfile::orderBy('name')
->paginate(10)
->through(fn($profile) => [
'id' => $profile->id,
'username' => $profile->username,
'name' => $profile->name,
]);
return Inertia::render('SmartDokProfile/Index', [
'profiles' => $profiles,
'filters' => $request->only(['search', 'page']),
]);
}
/**
* Show the form for creating a new resource.
*/
public function create() {
return Inertia::render('SmartDokProfile/Create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request) {
$data = $request->validate([
'username' => 'required|string|max:255|unique:smartdok_profiles,username',
'name' => 'required|string|max:255',
]);
// generate a UUID for the new profile
$data['id'] = (string) Str::uuid();
SmartdokProfile::create($data);
return redirect()
->route('smartdok.profiles.index')
->with('success', 'Profile created successfully.');
}
/**
* Display the specified resource.
*/
public function show(string $id) {
$profile = SmartdokProfile::findOrFail($id);
return Inertia::render('SmartDokProfile/Show', [
'profile' => [
'id' => $profile->id,
'username' => $profile->username,
'name' => $profile->name,
],
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id) {
$profile = SmartdokProfile::findOrFail($id);
return Inertia::render('SmartDokProfile/Edit', [
'profile' => [
'id' => $profile->id,
'username' => $profile->username,
'name' => $profile->name,
],
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id) {
$profile = SmartdokProfile::findOrFail($id);
$data = $request->validate([
'username' => 'required|string|max:255|unique:smartdok_profiles,username,' . $profile->id,
'name' => 'required|string|max:255',
]);
$profile->update($data);
return redirect()
->route('smartdok.profiles.index')
->with('success', 'Profile updated successfully.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id) {
$profile = SmartdokProfile::findOrFail($id);
$profile->delete();
return redirect()
->route('smartdok.profiles.index')
->with('success', 'Profile deleted successfully.');
}
}
+168
View File
@@ -0,0 +1,168 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Inertia\Inertia;
use App\Models\WorkHour;
use App\Models\SmartdokProfile;
use App\Models\Project;
class WorkHourController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request) {
$workHours = WorkHour::with(['user', 'project'])
->orderBy('created_at', 'desc')
->paginate(10)
->through(fn($wh) => [
'id' => $wh->id,
'user_id' => $wh->user_id,
'user_name' => $wh->user->name,
'ordinary_hours' => $wh->ordinary_hours,
'work_date' => $wh->work_date->toDateString(),
'comment' => $wh->comment,
'project_id' => $wh->project_id,
'project_name' => $wh->project->project_name,
]);
return Inertia::render('WorkHour/Index', [
'workHours' => $workHours,
'filters' => $request->only(['page']),
]);
}
/**
* Show the form for creating a new resource.
*/
public function create() {
$profiles = SmartdokProfile::orderBy('name')
->get()
->map(fn($p) => ['id' => $p->id, 'name' => $p->name]);
$projects = Project::orderBy('project_name')
->get()
->map(fn($proj) => ['id' => $proj->id, 'project_name' => $proj->project_name]);
return Inertia::render('WorkHour/Create', [
'profiles' => $profiles,
'projects' => $projects,
'today' => now()->toDateString(),
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request) {
$data = $request->validate([
'user_id' => 'required|uuid|exists:smartdok_profiles,id',
'project_id' => 'required|integer|exists:projects,id',
'ordinary_hours' => 'required|numeric|min:0.5|multiple_of:0.5',
'work_date' => 'required|date',
'comment' => 'nullable|string',
]);
WorkHour::create([
'user_id' => $data['user_id'],
'project_id' => $data['project_id'],
'ordinary_hours' => $data['ordinary_hours'],
'work_date' => $data['work_date'],
'comment' => $data['comment'] ?? null,
]);
return redirect()
->route('smartdok.work-hours.index')
->with('success', 'Work hour entry created successfully.');
}
/**
* Display the specified resource.
*/
public function show(string $id) {
$wh = WorkHour::with(['user', 'project'])->findOrFail($id);
return Inertia::render('WorkHour/Show', [
'workHour' => [
'id' => $wh->id,
'user_id' => $wh->user_id,
'user_name' => $wh->user->name,
'ordinary_hours' => $wh->ordinary_hours,
'work_date' => $wh->work_date->toDateString(),
'comment' => $wh->comment,
'project_id' => $wh->project_id,
'project_name' => $wh->project->project_name,
],
]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id) {
$wh = WorkHour::with(['user', 'project'])->findOrFail($id);
$profiles = SmartdokProfile::orderBy('name')
->get()
->map(fn($p) => ['id' => $p->id, 'name' => $p->name]);
$projects = Project::orderBy('project_name')
->get()
->map(fn($proj) => ['id' => $proj->id, 'project_name' => $proj->project_name]);
return Inertia::render('WorkHour/Edit', [
'workHour' => [
'id' => $wh->id,
'user_id' => $wh->user_id,
'ordinary_hours' => $wh->ordinary_hours,
'work_date' => $wh->work_date->toDateString(),
'comment' => $wh->comment,
'project_id' => $wh->project_id,
],
'profiles' => $profiles,
'projects' => $projects,
]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id) {
$wh = WorkHour::findOrFail($id);
$data = $request->validate([
'user_id' => 'required|uuid|exists:smartdok_profiles,id',
'project_id' => 'required|integer|exists:projects,id',
'ordinary_hours' => 'required|numeric|decimal:1|min:0.5',
'work_date' => 'required|date',
'comment' => 'nullable|string',
]);
$wh->update([
'user_id' => $data['user_id'],
'project_id' => $data['project_id'],
'ordinary_hours' => $data['ordinary_hours'],
'work_date' => $data['work_date'],
'comment' => $data['comment'] ?? null,
]);
return redirect()
->route('smartdok.work-hours.index')
->with('success', 'Work hour entry updated successfully.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id) {
WorkHour::findOrFail($id)->delete();
return redirect()
->route('smartdok.work-hours.index')
->with('success', 'Work hour entry deleted successfully.');
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\View;
use Symfony\Component\HttpFoundation\Response;
class HandleAppearance
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
View::share('appearance', $request->cookie('appearance') ?? 'system');
return $next($request);
}
}
@@ -0,0 +1,56 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Inspiring;
use Illuminate\Http\Request;
use Inertia\Middleware;
use Tighten\Ziggy\Ziggy;
class HandleInertiaRequests extends Middleware
{
/**
* The root template that's loaded on the first page visit.
*
* @see https://inertiajs.com/server-side-setup#root-template
*
* @var string
*/
protected $rootView = 'app';
/**
* Determines the current asset version.
*
* @see https://inertiajs.com/asset-versioning
*/
public function version(Request $request): ?string
{
return parent::version($request);
}
/**
* Define the props that are shared by default.
*
* @see https://inertiajs.com/shared-data
*
* @return array<string, mixed>
*/
public function share(Request $request): array
{
[$message, $author] = str(Inspiring::quotes()->random())->explode('-');
return [
...parent::share($request),
'name' => config('app.name'),
'quote' => ['message' => trim($message), 'author' => trim($author)],
'auth' => [
'user' => $request->user(),
],
'ziggy' => [
...(new Ziggy)->toArray(),
'location' => $request->url(),
],
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
];
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
/**
* Attempt to authenticate the request's credentials.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
/**
* Ensure the login request is not rate limited.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
/**
* Get the rate limiting throttle key for the request.
*/
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Settings;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'lowercase',
'email',
'max:255',
Rule::unique(User::class)->ignore($this->user()->id),
],
];
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
class ApiAdmins extends Model
{
use HasFactory;
protected $table = 'apiadmins';
protected $fillable = [
'user_id'
];
public function user(): BelongsTo {
return $this->belongsTo(User::class, 'user_id');
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ApiKeys extends Model
{
use HasFactory;
protected $table = 'apikeys';
protected $fillable = [
'category',
'key'
];
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Department extends Model
{
use HasFactory;
protected $fillable = [
'name',
];
/**
* The projects that belong to the department.
*/
public function projects(): BelongsToMany {
return $this->belongsToMany(
Project::class,
'department_project',
'department_id',
'project_id'
);
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Project extends Model
{
use HasFactory;
protected $fillable = [
'project_name',
'project_number',
];
/**
* The profiles associated with this project.
*/
public function profiles(): BelongsToMany {
return $this->belongsToMany(
SmartdokProfile::class,
'project_smartdok_profile',
'project_id',
'smartdok_profile_id'
);
}
/**
* The departments associated with this project.
*/
public function departments(): BelongsToMany {
return $this->belongsToMany(
Department::class,
'department_project',
'project_id',
'department_id'
);
}
/**
* The work hours entries linked to this project.
*/
public function workHours(): HasMany {
return $this->hasMany(WorkHour::class, 'project_id');
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class SmartdokProfile extends Model
{
use HasFactory;
protected $table = 'smartdok_profiles';
public $incrementing = false;
protected $keyType = 'string';
protected $fillable = [
'id',
'username',
'name',
];
/**
* The projects that belong to the profile.
*/
public function projects(): BelongsToMany {
return $this->belongsToMany(
Project::class,
'project_smartdok_profile',
'smartdok_profile_id',
'project_id'
);
}
/**
* The work hours entries for the profile.
*/
public function workHours(): HasMany {
return $this->hasMany(WorkHour::class, 'user_id', 'id');
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use App\Models\ApiAdmins;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array {
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function apiAdmin(): HasOne {
return $this->hasOne(ApiAdmins::class, 'user_id');
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class WorkHour extends Model
{
use HasFactory;
protected $table = 'work_hours';
protected $fillable = [
'user_id',
'project_id',
'ordinary_hours',
'work_date',
'comment'
];
protected $casts = [
'work_date' => 'date',
];
/**
* The profile that owns the work hour entry.
*/
public function user(): BelongsTo {
return $this->belongsTo(SmartdokProfile::class, 'user_id', 'id');
}
/**
* The project linked to this work hour entry.
*/
public function project(): BelongsTo {
return $this->belongsTo(Project::class, 'project_id');
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Dedoc\Scramble\Scramble;
use Dedoc\Scramble\Support\Generator\OpenApi;
use Dedoc\Scramble\Support\Generator\SecurityScheme;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void {
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void {
Scramble::configure()
->withDocumentTransformers(function (OpenApi $openApi) {
$openApi->secure(
SecurityScheme::http('bearer')
);
});
}
}