Lauantaiprojektit ovat omaksi iloksi tehtyjä kevyitä, perustoiminnallisuuksiltaan noin puolen päivän harrastusprojekteja. Jokaisen lauantaiprojektin lopputuloksen pyrin julkaisemaan GitHubissa avoimena lähdekoodina ja avaamaan synnytystarinan blogissani vaihe vaiheelta.
Lauantai-projektini oli jo maalisuoralla ja tämäkin artikkeli lähes julkaisukelpoinen, kun projekti romahti odottamatta kasaan täysin itsestä riippumattomista syistä. Aioin aluksi poistaa koko artikkelin ja jättää projektin julkaisematta. Lopulta päätin kuitenkin julkaista artikkelin ja rikkinäisen projektipohjan jakaen makeansuolaisen opetuksen: kaikki projektit eivät onnistu – ainakaan käytettävissä olevalla ajalla.
Ongelma projektin taustalla
Webkehittäjät, allekirjoittanut mukaan lukien, käsittelevät työssään päivittäin erittäin arkaluonteisia tietoja. Tällaisia ovat esimerkiksi erilaisten palvelimien, palveluiden ja SFTP-tilien käyttäjätunnukset. Markkinointitoimistoissa on käytössä toimivat prosessit ja työkalut tällaisten tietojen tietoturvalliseen hallintaan, mutta freelancereilla on harvemmin pääsyä sisäisiin työkaluihin tai tietoa pitää hankkia suoraan loppuasiakkailta.
Tietojen turvallinen välittäminen on kriittistä, jotta asiakkaan sivut eivät täyty viagra-mainoksista ja sähköpostilaatikot pysyvät poissa huijareiden käsistä. Toisaalta tietojen välittämisen tulee olla myös tarpeeksi helppoa, sähköpostien S/MIME tai PGP-salaus eivät aivan jokaiselta asiakkaalta luonnistu riippumatta annettujen ohjeiden perusteellisuudesta.
Sähköpostia en pidä alkuunkaan turvallisena tapana viestiä, ainakaan ilman viestien salaamista. Erityisesti turvattomuus syntyy viestien tallentumisesta laatikkoon toistaiseksi, jolloin mikä tahansa tulevaisuudessa löytyvä sähköposteihin liittyvä nollapäivähaavoittuvuus voi paljastaa nyt lähetettyä luottamuksellista tietoa joko lähettäjän tai vastaanottajan sähköpostilaatikosta. Sähköpostilaatikot ovat myös lähes poikkeuksetta suurten jenkkiyhtiöiden tarjoamia, joiden algoritmit käsittelevät viestien sisältöjä automatisoidusti.
Tietojen turvallinen välittäminen vaatisi, että
- tietojen sijaan välitetään linkki tiedot sisältävälle sivulle
- tietojen tulisi olla luettavissa vain kerran
- tietojen tulisi myös vanhentua automaattisesti, mahdollisimman pian sen jälkeen kun ne on säilötty tarkoituksenmukaisesti asiaan tarkoitetuihin salasanojen hallintatyökaluihin.
One-Time Secret
Poikkeuksetta tämänkin ongelman on jo joku toinen ratkaissut valmiiksi, ja julkaissut myös ilmaisen, avoimen lähdekoodin työkalun. Tässä tapauksessa palvelu on nimeltään One-Time Secret. Palveluun voit syöttää tietoa, jonka saa haltuunsa palvelusta saadusta linkistä asetetulla salasanalla ja halutun aika-ikkunan sisällä. Sisäänkirjautuneet käyttäjät voivat myös lähettää palvelussa linkin automaattisesti haluamaansa sähköpostiosoitteeseen.
Palvelu on kuitenkin englanniksi ja turvallisuus riippuu suuresti asetetusta salasanasta ja aika-ikkunan pituudesta. Palvelun hyödyntäminen vaatisi hieman kevyempää käyttöliittymää, jossa salasanan vahvuus ja aika-ikkunan lyhyys varmistettaisiin. Onneksi palvelu tarjoaa lähdekoodinsa lisäksi valmiin rajapinnan palvelun hyödyntämiseen.
Suunnitelmanani on luoda verkkosivu, jossa on vain yksi tekstikenttä ja Lähetä-painike. Vastaanottajan, eli minun, sähköpostiosoite, vahva salasana ja minimaalinen aika-ikkuna olisivat ennalta asetettuja. Sivulle voisi myös tarpeen mukaan kirjoittaa asiakkaalle ohjeita palvelun käyttämiseen.
Toteutustavan suhteen pallottelin kahden vaihtoehdon välillä: WordPress-lisäosan ja Next.js -sivun. Kehitysnopeuden vuoksi Next.js voitti, vaikka PHP:lle One-Time Secret tarjoaa valmista PHP-kirjastoa.
Vaihe 1: Projektin aloitus
Luodaan projektille GitHub-repositorio ja asennetaan Next.js startteri.
# luodaan Next.js -startteri
npx create-next-app@latest
# siirrytään kansioon
cd next-one-time-secret
# alustetaan versionhallinta
git init
# yhdistetään GitHub-etärepositorioon
git remote add origin https://github.com/oskarijarvelin/next-one-time-secret
# asennetaan projektin tarvitsemat lisäriippuvuudet
npm install base-64
# käynnistetään lokaali kehityspalvelin
npm run dev
Julkaisin repositorion Vercelissä sivustona domainissani salaisuus.oskarijarvelin.fi. Startteri näyttää oletuksena tältä:

Lisätään seuraavaksi projektiin tarvittavat ympäristömuuttujat API:n käyttämiseksi. Muuttujat saat nopeasti muuttamalla .env.example -tiedoston nimen muotoon .env.local ja täyttämällä omat tietosi muuttujiin.
OTS_USERNAME= # One-Time Secret -tilin käyttäjätunnus
OTS_API_TOKEN= # One-Time Secret -tilit API Token
OTS_PASSPHRASE= # Haluttu salasana salaisuuksille
OTS_TTL= # Haluttu salaisuuksien elinaika sekuntteina
OTS_RECIPIENT= # Haluttu salaisuuksien vastaanottaja
PUSHBULLET_TOKEN= # Pushbullet -tilin API Token
Tailwind nopeaan kehitykseen
Tailwind CSS mahdollistaa nopean UI:n kehittämisen ja hyvät perustyylit projektille. Tailwind on myös nopea lisätä Next.js -projektille.
# asennetaan kehittäjäriippuvuudet
npm install -D tailwindcss postcss autoprefixer @tailwindcss/forms
# alustetaan Tailwind ja luodaan tarvittavat asetustiedostot
npx tailwindcss init -p
# käynnistetään lokaalikehityspalvelin uudelleen
npm run dev
Lisätään Next.js:n tiedostopolut Tailwindin asetuksiin muokkaamalla tailwind.config.js -tiedostoa projektin juuressa.
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/forms'),
],
}
Lisätään Tailwindin pohjatyylit projektin tyylitiedostoon globals.css. Poistetaan kaikki samalla kaikki muut valmiiksi annetut tyylit sekä globals.css että Home.module.css -tiedostoista.
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: #FAFAFA;
}
Vaihe 2: Lomakkeen teko
Projekti keskittyy puhtaasti toiminnallisuuteen, joten luodaan aluksi riittävä ulkoasu toiminnon ympärille valmiin /src/pages/index.js -päälle.
import Head from "next/head";
import Link from "next/link";
export default function Home() {
/* Lomakkeen käsittelyfunktio tähän */
return (
<>
<Head>
<title>Lähetä salaisuus Oskarille</title>
<meta
name="description"
content="Lähetä salaisuus Oskarille tietoturvallisesti."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="container-lg">
<div className="mx-auto w-full max-w-lg my-20 bg-white shadow-md rounded px-8 pt-8 pb-8 mb-4">
<h1 className="text-3xl mb-6 font-bold">Lähetä salaisuus Oskarille</h1>
<p>
Tässä lomakkeella voit lähettää minulle tietoturvallisesti
mm. käyttäjätunnuksia, salasanoja ja muita
luottamuksellisia tietoja.
Salasanasuojattu linkki tietojen lukemiseen lähetetään
suoraan laitteilleni. Tiedot ovat luettavissa vain yhden
kerran 48 tunnin sisällä lähettämisestä.
</p>
{/* Lomake tulee tähän */}
</div>
<p className="text-center text-sm text-gray-600">
©
{' 2023 '}
<Link href="https://oskarijarvelin.fi">Oskari Järvelin</Link>
</p>
</main>
</>
);
}
Lisätään seuraavaksi varsinainen lomake
<form className="mt-12" onSubmit={handleSubmitForm}>
<div className="mb-6">
<label className="block font-bold mb-2" htmlFor="salaisuus">
Salaisuuden sisältö
</label>
<textarea
id="salaisuus"
name="salaisuus"
placeholder="Salasana: h4uk10nk4l4"
rows={4}
className="w-full shadow-md px-2 py-1 rounded-lg border-gray-300 text-gray-600"
required
></textarea>
</div>
<button className="w-full shadow-md rounded-lg py-2 px-4 bg-green-600 text-white font-semibold">
Lähetä salaisuus
</button>
</form>
Lisätään lomakkeen käsittelyfunktio handleSubmitForm() ennen Home-funktion palautusta. Lähetetään funktiossa lomakkeelta saatu salaisuus asiaa varten luomaamme API-reittiin /api/form. API-reittiä tarvittaan, sillä käsittelyfunktiossa ei pääse käsiksi salaisiin ympäristömuuttujiin paljastamatta niitä käyttäjien selaimiin. API-reitissä pääsemme käsiksi ympäristömuuttujiin, ja voimme tehdä tarvittavat API-yhteydet ulospäin turvallisesti.
const handleSubmitForm = (e) => {
e.preventDefault();
const lomake = {
salaisuus: e.target.salaisuus.value,
}
const response = fetch('/api/form', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(lomake),
});
};
Nyt lopputuloksena pitäisi olla visuaalisesti valmis sivusto salaisuuksien lähettämiseen.

Luodaan vielä /src/pages/index.js -tiedostoon ehdollinen onnistumisilmoitus lomakkeen vaihtoehtoiseksi sisällöksi. Lisätään oletuksena epätosi sent-tila, joka päivitetään todeksi handleSubmitForm-funktiossa. Näytetään tilan perusteella joko lomake tai onnistumisviesti.
{!sent &&
<form className="mt-12" onSubmit={handleSubmitForm} >
<div className="mb-6">
<label className="block font-bold mb-2" htmlFor="salaisuus">
Salaisuuden sisältö
</label>
<textarea
id="salaisuus"
name="salaisuus"
placeholder="Salasana: h4uk10nk4l4"
rows={4}
className="w-full shadow-md px-2 py-1 rounded-lg border-gray-300 text-gray-600"
required
></textarea>
</div>
<button className="w-full shadow-md rounded-lg py-2 px-4 bg-green-600 text-white font-semibold">
Lähetä salaisuus
</button>
</form>
}
{sent &&
<div>
<div className="rounded-lg mt-8 py-2 text-green-600">
<p className="font-bold text-xl">
Salaisuus lähetetty onnistuneesti.
</p>
</div>
<button className="w-full shadow-md rounded-lg mt-4 py-2 px-4 bg-green-600 text-white font-semibold" onClick={() => setSent(false)}>
Lähetä toinen salaisuus
</button>
</div>
}
Nyt /src/pages/index.js -tiedoston pitäisi näyttää tältä
import Head from "next/head";
import Link from "next/link";
import { useState } from "react";
export default function Home({ year }) {
const [sent, setSent] = useState(false);
const handleSubmitForm = (e) => {
e.preventDefault();
const lomake = {
salaisuus: e.target.salaisuus.value,
}
setSent(true);
const response = fetch('/api/form', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(lomake),
});
};
return (
<>
<Head>
<title>Lähetä salaisuus Oskarille</title>
<meta
name="description"
content="Lähetä salaisuus Oskarille tietoturvallisesti."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="container-lg">
<div className="mx-auto w-full max-w-lg my-20 bg-white shadow-md rounded px-8 pt-8 pb-8 mb-4">
<h1 className="text-3xl mb-6 font-bold">Lähetä salaisuus Oskarille</h1>
<p>
Tässä lomakkeella voit lähettää minulle tietoturvallisesti mm. käyttäjätunnuksia, salasanoja ja muita luottamuksellisia tietoja.
Salasanasuojattu linkki tietojen lukemiseen lähetetään suoraan laitteilleni.
Tiedot ovat luettavissa vain yhden kerran 48 tunnin sisällä lähettämisestä.
</p>
{!sent &&
<form className="mt-12" onSubmit={handleSubmitForm} >
<div className="mb-6">
<label className="block font-bold mb-2" htmlFor="salaisuus">
Salaisuuden sisältö
</label>
<textarea
id="salaisuus"
name="salaisuus"
placeholder="Salasana: h4uk10nk4l4"
rows={4}
className="w-full shadow-md px-2 py-1 rounded-lg border-gray-300 text-gray-600"
required
></textarea>
</div>
<button className="w-full shadow-md rounded-lg py-2 px-4 bg-green-600 text-white font-semibold">
Lähetä salaisuus
</button>
</form>
}
{sent &&
<div>
<div className="rounded-lg mt-8 py-2 text-green-600">
<p className="font-bold text-xl">
Salaisuus lähetetty onnistuneesti.
</p>
</div>
<button className="w-full shadow-md rounded-lg mt-4 py-2 px-4 bg-green-600 text-white font-semibold" onClick={() => setSent(false)}>
Lähetä toinen salaisuus
</button>
</div>
}
</div>
<p className="text-center text-sm text-gray-600">
©
{' '}{year}{' '}
<Link href="https://oskarijarvelin.fi">Oskari Järvelin</Link>
</p>
</main>
</>
);
}
export async function getServerSideProps() {
const year = await new Date().getFullYear();
return { props: { year } }
}
Vaihe 3: API -reitin teko
Next.js -projektipohjassa tulee mukana yksi esimerkkireitti /src/pages/api/hello.js, jonka pohjalta voimme luoda oman reitin lomakkeen käsittelylle. Luodaan tiedosto /src/pages/api/form.js.
Reitillä tarkistamme ensin saimmeko tyhjän salaisuuden, jolloin palautamme suoraan HTTP 400 -koodin. Sen jälkeen haetaan One-Time Secret -rajapinnan käyttöön tarvittavat tiedot ympäristömuuttujista.
Seuraavaksi lähetämme salaisuutemme OTS-rajapintaan ja kuuntelemme rajapinnan vastauksen. Mikäli vastaus on odotettu (sisältää käyttäjätunnuksemme custid -kentästä), suoritamme send_push() -funktion ja palautamme HTTP 201 -koodin lomakkeelle. Odottamattoman vastauksen tapauksessa palautamme HTTP 400 -koodin lomakkeelle virheen merkiksi.
Reitin alusta löytyvässä send_push() -funktiossa lähetämme linkin salaisuuden lukemiseen laitteilleni käyttäen Pushbulletin APIa. Salaisuuden avain saatiin OTS-rajapinnan vastauksesta.
import { encode as base64_encode } from "base-64";
function send_push(key) {
fetch(`https://api.pushbullet.com/v2/pushes`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Access-Token": `${process.env.PUSHBULLET_TOKEN}`,
},
body: JSON.stringify({
"body": `Salaisuuden ID on ${key}`,
"title": "Uusi salaisuus vastaanotettu",
"url": `https://salaisuus.oskarijarvelin.fi/lue/${key}`,
"type": "link"
}),
});
}
export default function handler(req, res) {
const body = req.body;
if (!body.salaisuus) {
return res.status(400).json({ data: "Salaisuus puuttuu" });
}
let ots = {
username: process.env.OTS_USERNAME,
api_token: process.env.OTS_API_TOKEN,
passphrase: process.env.OTS_PASSPHRASE,
recipient: process.env.OTS_RECIPIENT,
ttl: process.env.OTS_TTL,
};
fetch(
`https://onetimesecret.com/api/v1/share?secret=${encodeURI(
body.salaisuus
)}&passphrase=${ots.passphrase}&ttl=${ots.ttl}&recipient=${ots.recipient}`,
{
method: "POST",
headers: {
Authorization: `Basic ${base64_encode(
`${ots.username}:${ots.api_token}`
)}`,
},
}
)
.then((resp) => resp.json())
.then((resp) => {
if (resp?.custid === ots.username) {
send_push(resp.secret_key);
res.status(201).json({ success: true });
} else {
res
.status(400)
.json({ data: "Salaisuuden lähettämisessä tapahtui virhe" });
}
});
}
Vaihe 4: Salaisuuksien lukeminen
One-Time Secret lähettää automaattisesti sähköpostiin linkin salaisuuden lukemiseksi omassa palvelussaan. OTS:n API mahdollistaa kuitenkin myös salaisuuksien lukemisen. Eikun tuumasta toimeen, Next.js:n tarjoama dynaaminen reititys sopii kuin nenä päähän. Luodaan sivu /src/pages/lue/[id].js.
import Head from "next/head";
import Link from "next/link";
import { useState } from "react";
import { encode as base64_encode } from "base-64";
export default function Salaisuus({ data, year }) {
const [login, setLogin] = useState(false);
const handleLoginForm = (e) => {
e.preventDefault();
const lomake = {
email: e.target.email.value,
password: e.target.password.value,
}
const response = fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(lomake),
})
.then((response) => response.json())
.then((response) => {
if (response?.login === true) {
setLogin(true);
}
});
};
return (
<>
<Head>
<title>Lähetä salaisuus Oskarille</title>
<meta
name="description"
content="Lähetä salaisuus Oskarille tietoturvallisesti."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="container-lg">
<div className="mx-auto w-full max-w-lg my-20 bg-white shadow-md rounded px-8 pt-8 pb-8 mb-4">
{login &&
<>
<h1 className="text-3xl mb-8 font-bold">Salaisuus:</h1>
{data.value &&
<p>{data.value}</p>
}
{data.message &&
<p>Tämä salaisuus on jo luettu.</p>
}
<p className="mt-4 text-blue-900 font-bold">
<Link href="/">← Takaisin etusivulle</Link>
</p>
</>
}
{!login &&
<>
<h1 className="text-3xl mb-8 font-bold">Kirjaudu sisään</h1>
<form className="my-12" onSubmit={handleLoginForm} >
<div className="mb-6">
<label className="block font-bold mb-2" htmlFor="email">
Sähköposti
</label>
<input
type="email"
id="email"
name="email"
placeholder="Sähköpostiosoittesi"
className="w-full shadow-md px-2 py-2 rounded-lg border-gray-300 text-gray-600"
required
/>
</div>
<div className="mb-6">
<label className="block font-bold mb-2" htmlFor="password">
Salasana
</label>
<input
type="password"
id="password"
name="password"
placeholder="Salasanasi"
className="w-full shadow-md px-2 py-2 rounded-lg border-gray-300 text-gray-600"
required
/>
</div>
<button className="w-full shadow-md rounded-lg py-2 px-4 bg-green-600 text-white font-semibold">
Kirjaudu sisään
</button>
</form>
<p className="mt-4 text-blue-900 font-bold">
<Link href="/">← Takaisin etusivulle</Link>
</p>
</>
}
</div>
<p className="text-center text-sm text-gray-600">
©
{' '}{year}{' '}
<Link href="https://oskarijarvelin.fi">Oskari Järvelin</Link>
</p>
</main>
</>
);
}
export async function getServerSideProps(ctx) {
const year = await new Date().getFullYear();
const { id } = ctx.query;
let ots = {
username: process.env.OTS_USERNAME,
api_token: process.env.OTS_API_TOKEN,
passphrase: process.env.OTS_PASSPHRASE,
};
const res = await fetch(
`https://onetimesecret.com/api/v1/secret/${id}?passphrase=${ots.passphrase}`,
{
method: "POST",
headers: {
Authorization: `Basic ${base64_encode(
`${ots.username}:${ots.api_token}`
)}`,
},
}
);
const data = await res.json();
return { props: { data, year } };
}
Luodaan myös uusi API-reitti /src/pages/api/login.js autentikointia varten. Käytetään käyttäjätunnuksena OTS:n käyttäjänimeä (sähköpostiosoitetta) ja salasanana OTS:n passphrasea.
export default function handler(req, res) {
const body = req.body;
if (!body.email || !body.password) {
return res.status(401).json({ login: false });
}
if ( body.email === process.env.OTS_USERNAME && body.password === process.env.OTS_PASSPHRASE ) {
return res.status(200).json({ login: true });
} else {
return res.status(200).json({ login: false });
}
}
Nyt /src/pages/lue/[id].js näyttää aluksi kirjautumislomakkeen.

Onnistuneen kirjautumisen jälkeen näytetään joko salaisuus ensimmäisellä lukukerralla, tai virheilmoitus myöhemmillä lukuyrityksillä.

Palvelumme on nyt valmis lähettämään ja vastaanottamaan salaisuuksia. Lähdekoodin löydät tuttuun tapaan GitHubistani.
Jatkokehitysideat
Lauantaiprojekti on perusidealtaan hyvin lyhyt ja ominaisuuksiltaan karsittu kehitysprojekti. Tämän blogin jätän tähän, mutta projektia tulen varmasti tulevaisuudessa jatkokehittämään eteenpäin. Ja toteutuksen aion myös ottaa käyttöön asiakkailleni soveltuvissa määrin.
Tässä kohtaa jatkokehitysideoitani on mm.
- Lomakkeiden suojaus roskapostilta CAPTCHA:lla tai yksinkertaisemmalla hunajapurkilla.
- Salaisuus lähetetty onnistuneesti -viesti ei vielä kerro onko salaisuus todella lähetetty onnistuneesti. Salaisuuden lähettämisen mahdollisten virhetilanteiden indikoiminen lähetyksen jälkeen.
Projektin kaatuminen
Projekti oli käytännössä jo valmis, ja toimi lokaalissa kehitysympäristössä lähes moitteetta. Kehityksen aikana olin jo ihmetellyt muutamia erikoisuuksia One-Time Secretin käytössä. Ensinnäkään uuden salaisuuden luominen joko suoraan palvelussa tai rajapinnan kautta ei lähettänyt vastaanottajalle sähköpostiin linkkiä lupauksesta huolimatta. Myöskään palveluun rekisteröityminen tai kirjatuminen ei onnistunut suoraan epämääräisten virheilmoitusten vuoksi.
Vasta kun siirsin projektin lokaalista kehitysympäristöstä palvelimelle omaan subdomainiini, huomasin projektin olevan rikki. API-kutsu ei toimi oikein subdomainista käsin; vaikka salaisuus syntyy OTS:n paneeliin, ei järjestelmä saa vastausta OTS:ltä, jonka pohjalta sen pääsisi lukemaan.
Ongelmaa selviteltyäni alkoivat hälytyskellot soimaan: One-Time Secretin repositoriosta päätellen projekti oli jätetty kuolemaan ja ollut jo pidempään ilman aktiivista ylläpitoa. Viimeisimmistä muutoksista oli pitkä aika ja bugi-lista pursusi yli korjaamattomista ongelmista. Kaikki merkit ongelmista olisivat olleet nähtävissä jo lauantai-projektia suunnitellessani.
One-Time Secretin päälle ei ollut mahdollista rakentaa mitään kestävää ja/tai luotettavaa palvelua. Pohja koko lauantaiprojektilta putosi pois.
Mitä opin tästä projektista?
Erityisesti Next.js:n omat API-reitit olivat itselle varsin uusi asia. Oli erittäin mielenkiintoista päästä rakentamaan paitsi omia rajapintoja, niin myös integroimaan ulkopuolisia rajapintoja ketjuun osaksi oman rajapinnan toimintaa.
Myös lomakkeiden käsittelystä Next.js:llä sain kaipaamaani lisäkokemusta. Monipuolisemmat integraatiot ja lomakkeiden suojaaminen roskapostilta kaipaa yhä lisää tekemistä.
Avoimen lähdekoodin järjestelmiä hyödyntäessä tulee olla tarkkana järjestelmien elinvoimaisuuden ja elinkelpoisuuden varmistamisessa tulevaisuuden varalle tulee olla tarkkana. Elinvoimaisissa projekteissa on aktiivinen yhteisö takana, joka korjaa ongelmia pro-aktiivisesti. Elinkelpoisissa projekteissa on löydetty tavat rahoittaa järjestelmän ylläpito, maksullisten palveluiden tai sponsoreiden tuella. Yhden miehen harrastusprojektit ovat harvoin kumpaakaan.
Mitä seuraavaksi?
Projektin epäonnistumisesta huolimatta koen projektin olevan hyvä esimerkki projektin suunnittelusta, toteuttamisesta, kehittämisestä ja tietenkin myös mahdollisista vaaran paikoista. Tässä projektissa syntyi hyvä koodipohja Next.js -sovelluksiin sekä rajapintaintegraatioiden ketjuttamiseen, joten jätän repositorion julkisesti saataville käytettäväksi esimerkkinä vastaaviin projekteihin.
Tämän lauantaiprojektin lähtöidea syntyi kuitenkin liiketoiminnallisesta tarpeesta, vaikka ensimmäiseksi valitsemani toteutustapa päättyikin epäonnistumiseen. Mielessäni on jo seuraava toteutustapa, jota pohdin vaihtoehtona jo projektia suunnitellessani.
Aion seuraavaksi toteuttaa ideani WordPress-lisäosana, josta tulen kirjoittamaan uuden artikkelin; Lauantaiprojekti: Lähetä salaisuus turvallisemmin (osa 2).